🔹 Object-Oriented Programming (OOPs)
Definition: Object-Oriented Programming (OOP) is a programming paradigm based on the concept of “objects”, which can contain data (attributes or properties) and code (methods or functions). It allows developers to create modular, reusable, and organized code.
🔸 Classes and Objects
Class | A blueprint for creating objects. Defines attributes and methods. |
---|---|
Object | An instance of a class. It holds actual data and behaviors. |
🔹Example: Class and Object
class Employee:
pass
emp = Employee()
print(type(emp)) # <class '__main__.Employee'>
# Creating a class and an object
class Car:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def display_info(self):
print(f"Car Brand: {self.brand}, Model: {self.model}")
car1 = Car("Toyota", "Camry", 2023)
print(car1.display_info())
Output:
Car Brand: Toyota, Model: Camry
None
Instance Variables and Methods
Definition:
- Instance Variables are tied to a specific object
- Instance Methods operate on instances and can access/modify instance variables
class Customer:
def __init__(self, name, age):
self.name = name
self.age = age
def thank(self):
print(f"Hi {self.name}, thank you for visiting our store!")
cust1 = Customer("John", 3)
cust1.thank()
cust2 = Customer("Lucy", 4)
cust2.thank()
Output:
Hi John, thank you for visiting our store!
Hi Lucy, thank you for visiting our store!
Core Principles of OOP
- Encapsulation: Bundling data and methods that operate on the data within one unit (class).
- Abstraction: Hiding internal implementation and exposing only necessary details.
- Inheritance: A class can inherit attributes and methods from another class.
- Polymorphism: Use a shared interface for multiple forms (different behavior).
Encapsulation: : Encapsulation restricts direct access to variables. Use getters and setters to control access.
class Person:
def __init__(self):
self.__age = 0 # private variable
def get_age(self):
return self.__age
def set_age(self, age):
if age > 0:
self.__age = age
p = Person()
p.set_age(25)
print(p.get_age()) # 25
🔐 Access Modifiers in Python: Python doesn't have strict access control like languages such as Java or C++. Instead, it uses conventions to indicate the intended level of access to variables and methods.
- Public: name
- Protected: _name
- Private: __name
Examples
# Public
class Person:
def __init__(self):
self.name = "Alice"
p = Person()
print(p.name) # Allowed
# Protected
class Person:
def __init__(self):
self._status = "active"
p = Person()
print(p._status) # Discouraged
# Private
class Person:
def __init__(self):
self.__age = 25
p = Person()
# print(p.__age) # Error
print(p._Person__age) # Name mangling workaround
Access Modifiers Summary
Modifier | Syntax | Access Level | Usage |
---|---|---|---|
Public | name | Everywhere | Default |
Protected | _name | Subclass & internal | Convention |
Private | __name | Class only | Name mangled |
Python Access Modifiers Example
class Person:
def __init__(self, name, age):
self.name = name
self._status = "active"
self.__age = age
def get_age(self):
return self.__age
def set_age(self, age):
if age > 0:
self.__age = age
def _display_status(self):
print(f"Status: {self._status}")
def __private_info(self):
print("This is private information.")
def show_private_info(self):
self.__private_info()
p = Person("Alice", 30)
print("Name:", p.name)
print("Age:", p.get_age())
p.set_age(35)
print("Updated Age:", p.get_age())
print("Status:", p._status)
p._display_status()
p.show_private_info()
Output:
Name: Alice
Age: 30
Updated Age: 35
Status: active
Status: active
This is private information.
Abstraction: Abstraction hides internal details and shows only the functionality using abstract classes and methods.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def area(self):
print("Area of circle")
c = Circle()
c.area()
Output:
Area of circle
Inheritance: Inheritance allows a class to derive properties and behaviors from another class. The base class (parent class) provides properties and behaviors, while the child class (sub class) inherits and extends them.
# Basic Inheritance Example (Base Class)
class Employee:
def __init__(self, name, emp_id):
self.name = name
self.emp_id = emp_id
def display(self):
print(f"Name: {self.name}, ID: {self.emp_id}")
# Inheritance (Single Inheritance)
class Manager(Employee):
def __init__(self, name, emp_id, department):
super().__init__(name, emp_id)
self.department = department
def display(self):
super().display()
print(f"Department: {self.department}")
# Multiple Inheritance
class TechnicalSkills:
def __init__(self, skills):
self.skills = skills
def show_skills(self):
print(f"Skills: {', '.join(self.skills)}")
class Developer(Employee, TechnicalSkills):
def __init__(self, name, emp_id, skills):
Employee.__init__(self, name, emp_id)
TechnicalSkills.__init__(self, skills)
def display(self):
Employee.display(self)
self.show_skills()
# Sample Usage to test the above code snippets for inheritance
# Single Inheritance
mgr = Manager("Alice", 101, "HR")
mgr.display()
# Multiple Inheritance
dev = Developer("Bob", 202, ["Python", "Django", "REST API"])
dev.display()
Output:
Name: Alice, ID: 101
Department: HR
Name: Bob, ID: 202
Skills: Python, Django, REST API
Polymorphism: Polymorphism means the same method name can behave differently in different classes.
Polymorphism Example:
class Employee:
def __init__(self, name):
self.name = name
def work(self):
print(f"{self.name} is doing general employee tasks.")
class Manager(Employee):
def work(self):
print(f"{self.name} is managing team and projects.")
class Developer(Employee):
def work(self):
print(f"{self.name} is writing code and fixing bugs.")
class Designer(Employee):
def work(self):
print(f"{self.name} is creating UI/UX designs.")
# Usage for the above polymorphism code snippet
# List of different employee types
employees = [Manager("Alice"), Developer("Bob"), Designer("Charlie")]
# Polymorphic behavior: same method name, different results
for emp in employees:
emp.work()
Output:
Alice is managing team and projects.
Bob is writing code and fixing bugs.
Charlie is creating UI/UX designs.
Polymorphism with ABC (Abstract Base Class)
from abc import ABC, abstractmethod
class Employee(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def work(self):
pass
class Manager(Employee):
def work(self):
print(f"{self.name} is overseeing team operations.")
class Developer(Employee):
def work(self):
print(f"{self.name} is developing software solutions.")
class Intern(Employee):
def work(self):
print(f"{self.name} is learning and assisting in tasks.")
employees = [Manager("Alice"), Developer("Bob"), Intern("Charlie")]
for emp in employees:
emp.work()
Output:
Alice is overseeing team operations.
Bob is developing software solutions.
Charlie is learning and assisting in tasks.
Dunder (Double Underscore) Methods (Magic Methods)
class Book:
def __init__(self, title):
self.title = title
def __str__(self):
return f"Book title is {self.title}"
def __len__(self):
return len(self.title)
b = Book("Python Programming")
print(str(b)) # Book title is Python Programming
print(len(b)) # 18
🔹 Project: Employee Management System
This demonstrates all fource core OOP principles - Encapsulation, Abstraction, Inheritance, and Polymorphism
from abc import ABC, abstractmethod
# ABSTRACTION: Define an abstract base class
class Employee(ABC):
def __init__(self, name, emp_id, base_salary):
self._name = name
self._emp_id = emp_id
self._base_salary = base_salary
def get_details(self):
return f"ID: {self._emp_id}, Name: {self._name}"
@abstractmethod
def calculate_salary(self):
pass
# INHERITANCE: FullTimeEmployee inherits from Employee
class FullTimeEmployee(Employee):
def __init__(self, name, emp_id, base_salary, bonus):
super().__init__(name, emp_id, base_salary)
self._bonus = bonus
def calculate_salary(self):
return self._base_salary + self._bonus
# INHERITANCE: PartTimeEmployee inherits from Employee
class PartTimeEmployee(Employee):
def __init__(self, name, emp_id, hourly_rate, hours_worked):
super().__init__(name, emp_id, 0)
self._hourly_rate = hourly_rate
self._hours_worked = hours_worked
# POLYMORPHISM: Different implementation of calculate_salary
def calculate_salary(self):
return self._hourly_rate * self._hours_worked
# Demonstration
def print_salary(employee: Employee):
print(f"{employee.get_details()} => Salary: ${employee.calculate_salary()}")
# Test the project
emp1 = FullTimeEmployee("Alice", 101, 5000, 1200)
emp2 = PartTimeEmployee("Bob", 102, 20, 80)
print_salary(emp1)
print_salary(emp2)
Output:
ID: 101, Name: Alice => Salary: $6200
ID: 102, Name: Bob => Salary: $1600
🔍 Concepts in Action:
Encapsulation: _name, _emp_id, and salary fields are encapsulated (prefixed with _).
Abstraction: Employee is an abstract class with a common interface calculate_salary.
Inheritance: FullTimeEmployee and PartTimeEmployee inherit from Employee.
Polymorphism: print_salary() uses polymorphism to call the appropriate calculate_salary() method based on the object type.
✅ What is ABC in class Employee(ABC): • ABC stands for Abstract Base Class. • It comes from the built-in abc module in Python:
from abc import ABC