1.
Classes and Objects
Concept:
A class is a blueprint, and an object is an instance of that blueprint.
Real-world Example (E-commerce Product):
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# Objects
p1 = Product("Laptop", 60000)
p2 = Product("Phone", 25000)
print(p1.name, p1.price) # Laptop 60000
💡 Every product on Amazon/Flipkart is an object created from a Product class.
2. Constructors & Destructors
Concept:
Constructor (__init__) initializes values. Destructor (__del__) is called when an object
is destroyed.
Real-world Example (Bank Account):
class BankAccount:
def __init__(self, account_no):
self.account_no = account_no
print(f"Account {account_no} opened")
def __del__(self):
print(f"Account {self.account_no} closed")
acc = BankAccount(12345)
del acc
💡 When you open an account, data is initialized. When you close it, memory/resources are
released.
3. Static Members
Concept:
A static variable or method belongs to the class, not individual objects.
Real-world Example (Tracking number of employees in a company):
class Employee:
company_name = "TechCorp" # static variable
count = 0
def __init__(self, name):
self.name = name
Employee.count += 1
@staticmethod
def total_employees(name):
return Employee.count
e1 = Employee("Alice")
e2 = Employee("Bob")
print(Employee.total_employees(“alice”)) # 2
💡 Company HR software keeps track of total employees, not per employee.
4. Access Modifiers
Concept:
Control visibility of data:
o public – accessible everywhere
o protected (_var) – accessible within class & subclasses
o private (__var) – only inside class
Real-world Example (ATM – hide PIN number):
class ATM:
def __init__(self, pin):
self.__pin = pin # private
def validate_pin(self, pin):
return self.__pin == pin
atm = ATM(1234)
print(atm.validate_pin(1234)) # True
💡 You can’t directly access someone’s ATM PIN. You must go through validation.
5. Method Overloading & Overriding
Overloading (same method name, different inputs):
class Calculator:
def add(self, a, b, c=0):
return a + b + c
calc = Calculator()
print(calc.add(10, 20)) # 30
print(calc.add(10, 20, 30)) # 60
Overriding (child changes parent behavior):
class Vehicle:
def fuel(self,a,b):
return "Petrol/Diesel"
class ElectricCar(Vehicle):
def fuel(self,a):
return "Electric Charge"
print(ElectricCar().fuel()) # Electric Charge
a=electriccar()
a.fuel()
💡 Tesla overrides traditional fuel behavior with electric charging.
6. Composition vs Inheritance
Inheritance – "Is-a" relationship.
Composition – "Has-a" relationship.
Real-world Example (Car):
class Engine:
def start(self):
return "Engine starts"
class Car:
def __init__(self):
self.engine = Engine() # composition
def drive(self):
return self.engine.start() + " -> Car is moving"
print(Car().drive())
💡 A car has an engine (composition), but a Tesla is a car (inheritance).
7. Association, Aggregation, Composition
Association: Two classes are related but independent (Teacher–Student).
Aggregation: One class contains another, but both can exist independently
(Department–Professor).
Composition: One class owns another; if the parent is deleted, child is deleted
(House–Room).
💡 In school management software:
Teacher ↔ Student (association)
Department → Professor (aggregation)
House → Room (composition)
Association
Definition:
A loose relationship between two classes.
Both can exist independently, but they can be linked when needed.
Think of it as “uses” relationship.
Real-World Example: Teacher – Student
class Teacher:
def __init__(self, name):
self.name = name
class Student:
def __init__(self, name):
self.name = name
# Association: Teacher teaches Student
teacher = Teacher("Mr. John")
student = Student("Alice")
print(f"{teacher.name} teaches {student.name}")
👉 Even if the Teacher object is deleted, Student still exists and vice versa.
🔹 2. Aggregation
Definition:
A “Has-a” relationship.
One class contains another, but both can still exist independently.
The contained object can be shared among multiple owners.
Real-World Example: Department – Professor
class Professor:
def __init__(self, name):
self.name = name
class Department:
def __init__(self, dept_name):
self.dept_name = dept_name
self.professors = [] # Aggregation (collection of Professors)
def add_professor(self, prof):
self.professors.append(prof)
prof1 = Professor("Dr. Smith")
prof2 = Professor("Dr. Watson")
cs_dept = Department("Computer Science")
cs_dept.add_professor(prof1)
cs_dept.add_professor(prof2)
print(f"Department: {cs_dept.dept_name}")
for prof in cs_dept.professors:
print(f"- {prof.name}")
👉 Professor exists outside Department. If cs_dept is deleted, professors still exist
independently.
8. Operator Overloading
Concept: Redefining how operators work for objects.
Real-world Example (Money Addition in Wallet App):
class Wallet:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return Wallet(self.amount + other.amount)
w1 = Wallet(100)
w2 = Wallet(200)
w3 = w1 + w2
print(w3.amount) # 300
💡 PhonePe/Paytm combines wallet balances using operator overloading.
9. Abstract Classes & Interfaces
Concept: Defines rules, but actual implementation is left to subclasses.
Real-world Example (Payment Systems):
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCard(Payment):
def pay(self, amount):
return f"Paid {amount} via Credit Card"
class UPI(Payment):
def pay(self, amount):
return f"Paid {amount} via UPI"
print(CreditCard().pay(500))
print(UPI().pay(200))
💡 Different payment gateways follow a contract but implement differently.
10. Design Patterns Example (Singleton)
Singleton Pattern: Only one instance of a class is allowed.
Real-world Example (Database Connection):
class Database:
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = super(Database, cls).__new__(cls)
return cls.__instance
db1 = Database()
db2 = Database()
print(db1 is db2) # True
💡 Applications have only one active database connection at a time.
11. Dunder (Magic) Methods
Concept: Special methods in Python (__init__, __str__, __len__, etc.).
Real-world Example (Shopping Cart with __len__):
class Cart:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
cart = Cart(["Shoes", "Shirt", "Watch"])
print(len(cart)) # 3
1. Abstraction in Python
🔹 What it is:
Hiding internal implementation details and showing only the essential
functionality.
Achieved in Python using Abstract Base Classes (abc module).
A class becomes abstract when it has one or more abstract methods (methods
without implementation).
🔹 Real-world Example:
Think of an ATM machine:
You only see options → insert card, enter pin, withdraw money.
You don’t see the internal code: database checks, encryption, server
communication.
That hidden part is abstraction.
🔹 In Python:
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass # Only method signature, no body
class CreditCard(Payment):
def pay(self, amount):
print(f"Paid {amount} using Credit Card")
class UPI(Payment):
def pay(self, amount):
print(f"Paid {amount} using UPI")
# Using abstraction
p1 = CreditCard()
p1.pay(500) # Paid 500 using Credit Card
p2 = UPI()
p2.pay(300) # Paid 300 using UPI
🔹 Why & Where Used:
Why → To enforce a contract for subclasses, ensure consistency, and hide complex
details.
Where →
o Payment gateways (Credit card, UPI, PayPal → same pay() method).
o Vehicle system (Car, Bike, Bus → all must implement start_engine()).
o Frameworks and APIs where common rules are needed.
🦆 2. Duck Typing in Python
🔹 What it is:
Python doesn’t care about the class type of an object.
If an object has the required method/attribute, it can be used.
Called “Duck Typing” because →
“If it looks like a duck, swims like a duck, and quacks like a duck → it’s a duck.”
🔹 Real-world Example:
Think of a remote control:
You don’t care whether it’s a TV remote or an AC remote.
As long as it has a power button, you can use it to turn something ON.
Similarly in Python → As long as an object has the right method, you can use it.
🔹 In Python:
class Dog:
def speak(self): print("Bark")
class Cat:
def speak(self): print("Meow")
class Human:
def speak(self): print("Hello")
def make_sound(entity):
entity.speak() # Works as long as 'speak()' exists
make_sound(Dog()) # Bark
make_sound(Cat()) # Meow
make_sound(Human()) # Hello
👉 Here, Python didn’t check the type (Dog, Cat, Human) — it only checked if they had
speak().
🔹 Why & Where Used:
Why → Increases flexibility, avoids unnecessary inheritance, keeps code simple.
Where →
o Libraries like Pandas / NumPy → accept objects that behave like arrays (not
necessarily list).
o File-like objects → Anything with .read() can be used as a file (open(),
network stream, buffer).
o Testing → Mock objects can be used instead of real objects if they implement
the same methods.