👨👩👧👦 What is Inheritance?
Imagine a family where everyone has the same last name and some common traits:
Parents have certain abilities (cooking, driving, etc.)
Children automatically have those abilities too!
But children can also have their own special abilities
Inheritance in OOP works the same way:
A Parent Class (superclass) has some attributes and methods
A Child Class (subclass) automatically gets all of those
The child can add its own special stuff too!
Simple Definition : Inheritance = Child classes get everything from parent classes + can add their own stuff
🐾 The Classic Animal Example
Let’s start with the most common example - Animals!
# 👆 PARENT CLASS (Superclass)
class Animal :
def __init__ ( self , name , age ):
self .name = name
self .age = age
def eat ( self ):
print ( f " { self .name } is eating... 🍽️" )
def sleep ( self ):
print ( f " { self .name } is sleeping... 💤" )
def make_sound ( self ):
print ( f " { self .name } makes a sound" )
# 👇 CHILD CLASSES (Subclasses) - inherit from Animal
class Dog ( Animal ): # Dog IS-A Animal
def __init__ ( self , name , age , breed ):
super (). __init__ (name, age) # Call parent's __init__
self .breed = breed # Dog's own special attribute
def make_sound ( self ): # Override parent's method
print ( f " { self .name } says: Woof! Woof! 🐕" )
def fetch ( self ): # Dog's own special method
print ( f " { self .name } is fetching the ball! 🎾" )
class Cat ( Animal ): # Cat IS-A Animal
def __init__ ( self , name , age , color ):
super (). __init__ (name, age)
self .color = color
def make_sound ( self ):
print ( f " { self .name } says: Meow! 🐱" )
def climb ( self ): # Cat's special method
print ( f " { self .name } climbed the tree! 🌳" )
# Let's test it!
dog = Dog( "Buddy" , 3 , "Golden Retriever" )
cat = Cat( "Whiskers" , 2 , "Orange" )
# Both have parent's methods
dog.eat() # Buddy is eating... 🍽️
cat.eat() # Whiskers is eating... 🍽️
dog.sleep() # Buddy is sleeping... 💤
# Each has their own sound
dog.make_sound() # Buddy says: Woof! Woof! 🐕
cat.make_sound() # Whiskers says: Meow! 🐱
# Each has special abilities
dog.fetch() # Buddy is fetching the ball! 🎾
cat.climb() # Whiskers climbed the tree! 🌳
🔑 Key Concepts
1️⃣ The super() Function
super() is like calling your parent to help you:
class Parent :
def __init__ ( self , name ):
self .name = name
print ( f "Parent says: Hello, { name } !" )
class Child ( Parent ):
def __init__ ( self , name , toy ):
super (). __init__ (name) # 📞 Call parent's __init__ first!
self .toy = toy # Then add child's own stuff
print ( f "Child says: I have a { toy } !" )
kid = Child( "Tommy" , "robot" )
# Output:
# Parent says: Hello, Tommy!
# Child says: I have a robot!
2️⃣ Method Overriding
Children can replace parent methods with their own version:
class Bird ( Animal ):
def make_sound ( self ):
print ( f " { self .name } says: Tweet tweet! 🐦" )
# Override eat to be more specific
def eat ( self ):
print ( f " { self .name } is pecking at seeds... 🌻" )
sparrow = Bird( "Sparky" , 1 )
sparrow.eat() # Sparky is pecking at seeds... 🌻 (Bird's version)
3️⃣ The IS-A Relationship
Inheritance creates an “IS-A” relationship:
Dog IS-A Animal ✅
Cat IS-A Animal ✅
Car IS-A Animal ❌ (doesn’t make sense!)
dog = Dog( "Rex" , 5 , "German Shepherd" )
print ( isinstance (dog, Dog)) # True
print ( isinstance (dog, Animal)) # True - Dog IS-A Animal!
print ( isinstance (dog, Cat)) # False - Dog is NOT a Cat
🎮 Fun Example: RPG Character Classes
Let’s build a game with different character types:
class Character :
"""Base class for all game characters"""
def __init__ ( self , name , health = 100 , mana = 50 ):
self .name = name
self .health = health
self .max_health = health
self .mana = mana
self .level = 1
def take_damage ( self , amount ):
self .health -= amount
if self .health <= 0 :
self .health = 0
print ( f "💀 { self .name } has fallen!" )
else :
print ( f "😣 { self .name } took { amount } damage! HP: { self .health } / { self .max_health } " )
def heal ( self , amount ):
self .health = min ( self .health + amount, self .max_health)
print ( f "💚 { self .name } healed for { amount } ! HP: { self .health } / { self .max_health } " )
def attack ( self , target ):
damage = 10 # Base damage
print ( f "⚔️ { self .name } attacks { target.name } !" )
target.take_damage(damage)
# 🧙♂️ MAGE - Magic specialist
class Mage ( Character ):
def __init__ ( self , name ):
super (). __init__ (name, health = 80 , mana = 150 ) # Less health, more mana
self .spells = [ "Fireball" , "Ice Blast" , "Lightning" ]
def cast_spell ( self , spell_name , target ):
if spell_name not in self .spells:
print ( f "❌ { self .name } doesn't know { spell_name } !" )
return
if self .mana < 20 :
print ( f "❌ Not enough mana!" )
return
self .mana -= 20
damage = 30 # Magic is powerful!
print ( f "✨ { self .name } casts { spell_name } !" )
target.take_damage(damage)
# 🛡️ WARRIOR - Tank specialist
class Warrior ( Character ):
def __init__ ( self , name ):
super (). __init__ (name, health = 150 , mana = 20 ) # More health, less mana
self .armor = 10
self .is_defending = False
def take_damage ( self , amount ): # Override to use armor
if self .is_defending:
amount = amount // 2
print ( f "🛡️ { self .name } blocks half the damage!" )
self .is_defending = False
actual_damage = max ( 0 , amount - self .armor)
super ().take_damage(actual_damage) # Call parent's take_damage
def defend ( self ):
self .is_defending = True
print ( f "🛡️ { self .name } raises their shield!" )
def heavy_strike ( self , target ):
print ( f "💪 { self .name } uses HEAVY STRIKE!" )
target.take_damage( 25 )
# 🏹 ARCHER - Range specialist
class Archer ( Character ):
def __init__ ( self , name ):
super (). __init__ (name, health = 90 , mana = 60 )
self .arrows = 20
def shoot_arrow ( self , target ):
if self .arrows <= 0 :
print ( f "❌ { self .name } is out of arrows!" )
return
self .arrows -= 1
print ( f "🏹 { self .name } shoots an arrow! (Arrows left: { self .arrows } )" )
target.take_damage( 18 )
def multi_shot ( self , targets ):
"""Shoot 3 arrows at multiple targets!"""
if self .arrows < 3 :
print ( f "❌ Need 3 arrows for multi-shot!" )
return
self .arrows -= 3
print ( f "🏹🏹🏹 { self .name } uses MULTI-SHOT!" )
for target in targets[: 3 ]:
target.take_damage( 12 )
# 🎮 Let's play!
mage = Mage( "Gandalf" )
warrior = Warrior( "Conan" )
archer = Archer( "Legolas" )
print ( "=== Battle Begins! === \n " )
# Mage attacks warrior
mage.cast_spell( "Fireball" , warrior)
# Warrior defends then attacks
warrior.defend()
mage.attack(warrior) # Will be blocked
# Warrior counter attacks
warrior.heavy_strike(mage)
# Archer joins the fight
archer.shoot_arrow(mage)
print ( f " \n === Final Status ===" )
print ( f "🧙♂️ { mage.name } : { mage.health } HP, { mage.mana } Mana" )
print ( f "🛡️ { warrior.name } : { warrior.health } HP" )
print ( f "🏹 { archer.name } : { archer.health } HP, { archer.arrows } Arrows" )
📚 Types of Inheritance
Multiple Inheritance Multiple parents Flyable Swimmable
↘ ↙
Duck
Hierarchical Inheritance One parent, many children Animal
/ | \
Dog Cat Bird
Multi-level Example
class Animal :
def breathe ( self ):
print ( "Breathing... 💨" )
class Mammal ( Animal ): # Inherits from Animal
def feed_milk ( self ):
print ( "Feeding milk to babies 🍼" )
class Dog ( Mammal ): # Inherits from Mammal (and thus Animal)
def bark ( self ):
print ( "Woof! 🐕" )
dog = Dog()
dog.breathe() # From Animal ✅
dog.feed_milk() # From Mammal ✅
dog.bark() # From Dog ✅
Multiple Inheritance Example
class Flyable :
def fly ( self ):
print ( "Flying through the sky! 🦅" )
class Swimmable :
def swim ( self ):
print ( "Swimming in water! 🏊" )
class Duck ( Flyable , Swimmable ): # Inherits from BOTH
def quack ( self ):
print ( "Quack quack! 🦆" )
duck = Duck()
duck.fly() # From Flyable ✅
duck.swim() # From Swimmable ✅
duck.quack() # From Duck ✅
print ( "A duck can do everything!" )
⚠️ When NOT to Use Inheritance
Don’t use inheritance just to share code! Use it when there’s a real IS-A relationship.
❌ Wrong: Stack inherits from List
# BAD: A Stack IS-NOT-A List (even if they share some behavior)
class Stack ( list ): # Don't do this!
def push ( self , item ):
self .append(item)
def pop ( self ):
return super ().pop()
# Problem: Stack now has all list methods!
stack = Stack()
stack.push( 1 )
stack.push( 2 )
stack.insert( 0 , 100 ) # 😱 Users can break the stack behavior!
✅ Right: Use Composition Instead
# GOOD: Stack HAS-A list inside (composition)
class Stack :
def __init__ ( self ):
self ._items = [] # Stack HAS a list
def push ( self , item ):
self ._items.append(item)
def pop ( self ):
return self ._items.pop()
def peek ( self ):
return self ._items[ - 1 ] if self ._items else None
def is_empty ( self ):
return len ( self ._items) == 0
# Now users can only use stack operations!
stack = Stack()
stack.push( 1 )
stack.push( 2 )
# stack.insert(0, 100) # ❌ Error! No insert method available
🧪 Practice Exercise
Challenge: Build a Vehicle Hierarchy
Create a vehicle inheritance system:
Vehicle (parent): has brand, model, and a start() method
Car (child): adds number of doors, and honk() method
Motorcycle (child): adds engine_cc, and wheelie() method
ElectricCar (child of Car): adds battery_capacity, and charge() method
class Vehicle :
def __init__ ( self , brand , model ):
# TODO : Initialize brand and model
pass
def start ( self ):
# TODO : Print starting message
pass
class Car ( Vehicle ):
# TODO : Add doors, override start to say "Car starting..."
pass
class Motorcycle ( Vehicle ):
# TODO : Add engine_cc, add wheelie() method
pass
class ElectricCar ( Car ):
# TODO : Add battery_capacity, add charge() method
pass
# Test
tesla = ElectricCar( "Tesla" , "Model 3" , 4 , 75 )
tesla.start() # Should work (from Car/Vehicle)
tesla.honk() # Should work (from Car)
tesla.charge() # Should work (from ElectricCar)
📝 Quick Summary
Concept Description Example Parent Class The class being inherited from AnimalChild Class The class that inherits Dog(Animal)super() Calls parent’s methods super().__init__()Override Replace parent’s method Redefine make_sound() in child IS-A The relationship test Dog IS-A Animal ✅
🏃 Next Up: Polymorphism
Now that you can create family trees of classes, let’s learn how the same method can behave differently for different objects!
Continue to Polymorphism → Learn how one interface can work with many different types - like a universal remote!