Skip to main content

Pillar 1: Inheritance

Now that you have a complete mental model of objects, memory, references, constructors, and self, you are ready to learn the Four Pillars of OOP.

The First Pillar is Inheritance.


1. Learning Objective

By the end of this lesson, you will understand:

  • How Inheritance allows a child class to reuse attributes and methods from a parent class.
  • How Python's method resolution order (MRO) searches for attributes in memory.
  • What super().__init__() actually does under the hood.
  • How to design clean parent/child class hierarchies without code duplication.

2. Why This Concept Exists: Vehicle Fleet Case Study

Imagine building a vehicle tracking app for a company managing Electric Cars and Petrol Cars.

Let's look at the attributes required for both vehicle types:

  • ElectricCar: brand, model, price, battery_capacity_kwh, charging_time_hrs, start_engine()
  • PetrolCar: brand, model, price, fuel_tank_capacity_l, mileage_kpl, start_engine()

Notice that brand, model, price, and start_engine() are 100% identical in both classes!

Writing these fields twice causes code redundancy. If you need to update how start_engine() works, you would have to edit code in 20 separate classes.

Inheritance solves this by extracting common attributes into a single parent base class (Vehicle).


3. Common Beginner Confusions

Confusion 1: "Does Inheritance create two separate objects in memory?"

  • Answer: NO! Creating an instance of ElectricCar allocates ONE single object in Heap RAM. That single object contains both inherited parent attributes (brand, model, price) AND child-specific attributes (battery_capacity_kwh).

Confusion 2: "Why do we need super().__init__()?"

  • Answer: super().__init__() invokes the parent class constructor to populate common parent attributes on the current object reference (self). If you omit super().__init__(), parent attributes like self.brand will never be created!

4. Mental Model: Base Vehicle Chassis

Think of Inheritance as a Modular Car Factory:

  1. Parent Base Class (Vehicle):
    • The factory manufactures a standard base chassis containing an engine starter button, brand badge, and wheels.
  2. Child Classes (ElectricCar / PetrolCar):
    • ElectricCar takes the base chassis and installs a high-voltage battery and charging port.
    • PetrolCar takes the exact same base chassis and installs a petrol tank and fuel gauge.

Both vehicles inherit the base chassis features while customizing their specific components!


5. Internal Python Execution Flow

Let's trace what happens when executing ev = ElectricCar("Tata", "Nexon EV", 1500000, 40.5, 6):

Code Executed: ev = ElectricCar("Tata", "Nexon EV", 1500000, 40.5, 6)

↓ Execution Trace:

1. Python allocates a fresh empty Heap Object (Address 0xA000).
2. Python calls ElectricCar.__init__(0xA000, "Tata", "Nexon EV", 1500000, 40.5, 6).
3. Inside ElectricCar.__init__, Python hits: super().__init__("Tata", "Nexon EV", 1500000).
4. super() resolves Parent Class -> 'Vehicle'.
5. Vehicle.__init__(0xA000, "Tata", "Nexon EV", 1500000) runs.
6. Vehicle.__init__ sets self.brand, self.model, self.price on Heap Object 0xA000.
7. Control returns to ElectricCar.__init__.
8. ElectricCar.__init__ sets self.battery_capacity_kwh and self.charging_time_hrs on Heap Object 0xA000.
9. Object 0xA000 contains all 5 attributes. Reference 0xA000 is assigned to variable 'ev'.

6. Memory Visualization: Single Heap Object Layout

RAM Layout: Unified Heap Memory for Inherited Object

STACK MEMORY

ev  =>  0xA000

SINGLE HEAP OBJECT (0xA000)

INHERITED FROM VEHICLE (PARENT):
brand = "Tata"
model = "Nexon EV"
price = 1500000
CHILD SPECIFIC (ELECTRICCAR):
battery_capacity_kwh = 40.5
charging_time_hrs = 6

7. Python Code Implementation

# 1. Parent Base Class
class Vehicle:
def __init__(self, brand, model, price):
self.brand = brand
self.model = model
self.price = price

def start_engine(self):
print(f"Engine started for {self.brand} {self.model}.")


# 2. Child Class 1: ElectricCar inherits from Vehicle
class ElectricCar(Vehicle):
def __init__(self, brand, model, price, battery_capacity_kwh, charging_time_hrs):
# Call parent constructor to populate common attributes
super().__init__(brand, model, price)

# Child-specific attributes
self.battery_capacity_kwh = battery_capacity_kwh
self.charging_time_hrs = charging_time_hrs

def charge_battery(self):
print(f"Charging {self.brand} {self.model}... Estimated time: {self.charging_time_hrs} hours.")


# 3. Child Class 2: PetrolCar inherits from Vehicle
class PetrolCar(Vehicle):
def __init__(self, brand, model, price, fuel_tank_capacity_l, mileage_kpl):
# Call parent constructor to populate common attributes
super().__init__(brand, model, price)

# Child-specific attributes
self.fuel_tank_capacity_l = fuel_tank_capacity_l
self.mileage_kpl = mileage_kpl

def refuel(self):
print(f"Refueling {self.brand} {self.model} with petrol...")


# --- Usage ---
ev = ElectricCar("Tata", "Nexon EV", 1500000, 40.5, 6)
car = PetrolCar("Maruti", "Swift", 700000, 37, 22)

# Accessing inherited parent method
ev.start_engine()
car.start_engine()

# Accessing specific child methods
ev.charge_battery()
car.refuel()

Output:

Engine started for Tata Nexon EV.
Engine started for Maruti Swift.
Charging Tata Nexon EV... Estimated time: 6 hours.
Refueling Maruti Swift with petrol...

8. Line-by-Line Execution Analysis

Line: class ElectricCar(Vehicle):

  • Syntax: (Vehicle) inside parentheses informs Python that ElectricCar derives from parent class Vehicle.

Line: super().__init__(brand, model, price)

  • Function super(): Returns a proxy object delegating method calls to parent class Vehicle.
  • Execution: Runs Vehicle.__init__, attaching self.brand, self.model, and self.price onto current object reference self.

9. Before / After Memory Visualization

Before super().__init__() runs inside ElectricCar

  • Heap Address 0xA000 is an unpopulated object box {}.

After super().__init__() finishes

  • Heap Address 0xA000 contains {"brand": "Tata", "model": "Nexon EV", "price": 1500000}.

After ElectricCar.__init__() finishes

  • Heap Address 0xA000 contains all 5 fields {"brand": "Tata", "model": "Nexon EV", "price": 1500000, "battery_capacity_kwh": 40.5, "charging_time_hrs": 6}.

10. Illustration Prompt for Diagram Generation

Excalidraw Diagram Prompt: A hand-drawn diagram illustrating Inheritance and super() execution flow in Python. At top, draw a blue box labeled "Parent Class: Vehicle" with attributes (brand, model, price) and method "start_engine()". Below, draw two green boxes labeled "Child: ElectricCar" and "Child: PetrolCar" with solid arrows pointing up labeled "inherits from". Draw a 4-step sequence arrow showing:

  1. "ElectricCar('Tata', ...)" creates Heap Box 0xA000.
  2. "super().init()" calls Vehicle.init to write parent attributes into 0xA000.
  3. ElectricCar writes specific attributes into 0xA000.
  4. Variable "ev" receives reference 0xA000. White background, clean hand-drawn style, colors: Blue (Parent), Green (Child/Heap), Purple (Stack Reference).

Quick Summary

  • Inheritance allows child classes to inherit attributes and methods from a parent base class using class Child(Parent):.
  • Inheritance creates ONE unified object in Heap RAM containing both parent and child attributes.
  • super().__init__() delegates initialization of common parent attributes to the parent constructor.
  • Inheritance eliminates duplicate code and organizes software into clean hierarchical models.

What's Next?