0–5 min
Real Hook
5–25 min
Core Concept
25–45 min
Live Coding
45–55 min
You Code It
55–60 min
Cheat Sheet
🔥 Real-World Hook (0–5 min)

Why does updating Instagram not require reinstalling your phone's OS? Why does a junior developer at Swiggy only need to change one Restaurant class to update how all restaurants behave across the app?

Object-Oriented Programming. Everything is a blueprint (class). One change in the blueprint updates every object built from it. This is how Python backends at Swiggy, Razorpay, and Urban Company are structured.

🌍 The 4 Pillars — Python Style (5–25 min)

💡 Analogies

Encapsulation — A capsule hides the medicine. You swallow it, not mix chemicals. Private attributes + public methods.
Inheritance — A SavingsAccount IS a BankAccount. Gets everything from parent, adds its own rules.
Polymorphism — area() on a Circle vs Rectangle — same name, different result.
Abstraction — You drive a car without knowing the engine. Abstract base class hides complexity.

💻 Live Coding — Bank Account Hierarchy (25–45 min)

class BankAccount:
    # __init__ is Python's constructor
    def __init__(self, owner, balance=0):
        self.owner   = owner
        self._balance = balance      # _ prefix = "treat as private"
        self._transactions = []

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            self._transactions.append(amount)

    def withdraw(self, amount):
        if amount > self._balance:
            print("Insufficient funds!")
        else:
            self._balance -= amount

    def get_balance(self):
        return self._balance

    def __str__(self):              # what print() shows — dunder method
        return f"{self.owner}: ₹{self._balance}"


class SavingsAccount(BankAccount):  # inherits BankAccount
    MIN_BALANCE = 500

    def withdraw(self, amount):          # Polymorphism: override parent method
        if self._balance - amount < self.MIN_BALANCE:
            print("Cannot withdraw — min balance ₹500 required")
        else:
            super().withdraw(amount)    # call parent's method


class LoanAccount(BankAccount):
    def __init__(self, owner, balance, rate):
        super().__init__(owner, balance)
        self.rate = rate

    def calculate_interest(self, months):
        return self._balance * self.rate * months / 1200


# Demo
acc  = BankAccount("Priya", 10000)
sav  = SavingsAccount("Ravi", 10000)
loan = LoanAccount("Anita", 50000, 12)   # 12% p.a.

sav.withdraw(9600)            # "Cannot withdraw — min balance ₹500 required"
sav.withdraw(5000)            # OK
print(sav)                      # "Ravi: ₹5000"
print(loan.calculate_interest(6))# ₹3000
🎯 You Code It (45–55 min)
  1. Create an Employee class: name, salary; method work() prints "{name} is working"
  2. Create Manager(Employee) — add team_size, override work() to print "{name} is managing {team_size} people"
  3. Create Contractor(Employee) — add hours_worked, add method calculate_pay() returning hours × ₹500
  4. Add __str__ to Employee that shows name + salary
  5. Create one of each, call work() on each — see polymorphism: same method, different output
  6. Show: Print all three objects + all three work() outputs

📄 Day 4 Cheat Sheet — OOP in Python

Class Basics

class Car:Define class
def __init__(self, x):Constructor
self.speed = xInstance attribute
car = Car(100)Create object
car.speedAccess attribute
_nameConvention: private

Inheritance

class Dog(Animal):Inherit
super().__init__(x)Call parent constructor
super().method()Call parent method
override methodJust redefine it
isinstance(obj, Cls)Check type

Dunder Methods

__init__Constructor
__str__What print() shows
__repr__Developer string
__len__len(obj)
__eq__obj1 == obj2
__lt__obj1 < obj2

Class vs Instance

class X: count = 0Class attribute (shared)
self.name = xInstance attribute (unique)
@classmethodMethod on the class itself
@staticmethodUtility, no self/cls
@propertyGetter with dot syntax