- What is multilevel inheritance — a chain of inheritance
- The inheritance chain — how classes are connected
- Grandparent, parent, and child — the hierarchy
- Method lookup — how Python finds methods
- super() in the chain — calling ancestor methods
- Constructors — initializing the whole chain
What is Multilevel Inheritance?
Multilevel inheritance is a type of inheritance where a class inherits from another class, which in turn inherits from another class. This creates a chain of inheritance, like a family tree where each generation passes down traits to the next.
Think of it like a family line: a grandparent passes traits to a parent, who passes traits to a child. Each generation adds its own unique characteristics while inheriting from the previous generation.
In Python, multilevel inheritance creates a hierarchy where each class inherits from the one above it. This allows you to build increasingly specialized classes while reusing code from the levels above.
💡 Key concept: Multilevel inheritance is a chain of inheritance where each class inherits from the class above it. It creates a hierarchy from general to specific.
The Inheritance Chain
Understanding the Chain
In multilevel inheritance, each class in the chain inherits from the class above it. This creates a linear hierarchy where each class builds on the previous ones.
# The inheritance chain
# Grandparent class (Level 1)
class Animal:
"""The base class for all animals"""
def __init__(self, name):
self.name = name
print(f"Animal __init__ called for {name}")
def eat(self):
return f"{self.name} is eating"
def sleep(self):
return f"{self.name} is sleeping"
# Parent class (Level 2) - inherits from Animal
class Mammal(Animal):
"""A class representing mammals"""
def __init__(self, name, fur_color):
super().__init__(name)
self.fur_color = fur_color
print(f"Mammal __init__ called for {name}")
def feed_milk(self):
return f"{self.name} is feeding milk"
def warm_blooded(self):
return True
# Child class (Level 3) - inherits from Mammal
class Dog(Mammal):
"""A class representing dogs"""
def __init__(self, name, fur_color, breed):
super().__init__(name, fur_color)
self.breed = breed
print(f"Dog __init__ called for {name}")
def bark(self):
return f"{self.name} says Woof!"
def fetch(self):
return f"{self.name} is fetching"
# Grandchild class (Level 4) - inherits from Dog
class Puppy(Dog):
"""A class representing puppies"""
def __init__(self, name, fur_color, breed, age):
super().__init__(name, fur_color, breed)
self.age = age
print(f"Puppy __init__ called for {name}")
def play(self):
return f"{self.name} is playing"
# Override the bark method
def bark(self):
return f"{self.name} says Yip Yip!"
# Creating a Puppy object
print("Creating a Puppy...")
puppy = Puppy("Max", "Golden", "Golden Retriever", 3)
# Methods from Animal (Level 1)
print(puppy.eat()) # Max is eating
print(puppy.sleep()) # Max is sleeping
# Methods from Mammal (Level 2)
print(puppy.feed_milk()) # Max is feeding milk
print(f"Warm blooded: {puppy.warm_blooded()}") # True
# Methods from Dog (Level 3)
print(puppy.fetch()) # Max is fetching
# Methods from Puppy (Level 4)
print(puppy.play()) # Max is playing
# Overridden method
print(puppy.bark()) # Max says Yip Yip!
# The inheritance chain:
# Puppy → Dog → Mammal → Animal → object
print("Inheritance chain:")
for cls in Puppy.__mro__:
print(f" {cls.__name__}")
The inheritance chain key points:
- Linear chain — each class has exactly one parent
- Levels — each level adds more specific functionality
- All ancestors — child inherits from all classes above it
- MRO — follows the chain from child up to object
- Specialization — each level becomes more specific
Quick Check: What is the inheritance chain in multilevel inheritance? (Answer: A linear chain where each class inherits from the class above it)
Grandparent, Parent, and Child
The Three Generations of Inheritance
In multilevel inheritance, we often talk about three generations: grandparent, parent, and child. Each generation adds more specific functionality while inheriting from the generation above.
# Grandparent, Parent, and Child
# Grandparent class (most general)
class Vehicle:
"""A class representing a vehicle"""
def __init__(self, brand, year):
self.brand = brand
self.year = year
self.is_moving = False
def start(self):
self.is_moving = True
return f"{self.brand} vehicle started"
def stop(self):
self.is_moving = False
return f"{self.brand} vehicle stopped"
def get_info(self):
return f"{self.year} {self.brand} vehicle"
# Parent class (more specific)
class Car(Vehicle):
"""A class representing a car"""
def __init__(self, brand, year, model, doors=4):
super().__init__(brand, year)
self.model = model
self.doors = doors
self.gps_enabled = False
def enable_gps(self):
self.gps_enabled = True
return f"GPS enabled for {self.brand} {self.model}"
def get_info(self):
return f"{self.year} {self.brand} {self.model} with {self.doors} doors"
# Child class (most specific)
class SportsCar(Car):
"""A class representing a sports car"""
def __init__(self, brand, year, model, doors=2, horsepower=400):
super().__init__(brand, year, model, doors)
self.horsepower = horsepower
self.top_speed = 180
def accelerate(self):
return f"{self.brand} {self.model} is accelerating to {self.top_speed} mph"
def get_info(self):
return f"{super().get_info()} and {self.horsepower} HP"
# Creating objects
print("=== Vehicle (Grandparent) ===")
vehicle = Vehicle("Generic", 2020)
print(vehicle.get_info()) # 2020 Generic vehicle
print("\n=== Car (Parent) ===")
car = Car("Toyota", 2022, "Camry", 4)
print(car.get_info()) # 2022 Toyota Camry with 4 doors
print(car.enable_gps()) # GPS enabled for Toyota Camry
print("\n=== SportsCar (Child) ===")
sports_car = SportsCar("Ferrari", 2023, "F8", 2, 710)
print(sports_car.get_info()) # 2023 Ferrari F8 with 2 doors and 710 HP
print(sports_car.accelerate()) # Ferrari F8 is accelerating to 180 mph
# Checking the hierarchy
print(f"Is SportsCar a Car? {issubclass(SportsCar, Car)}") # True
print(f"Is SportsCar a Vehicle? {issubclass(SportsCar, Vehicle)}") # True
print(f"Is SportsCar an object? {issubclass(SportsCar, object)}") # True
Three generations key points:
- Grandparent — most general, common functionality
- Parent — more specific, extends grandparent
- Child — most specific, extends parent
- Each level — adds its own unique attributes and methods
- Inheritance flows down — child has access to all methods from above
Quick Check: What are the three generations in multilevel inheritance? (Answer: Grandparent, parent, and child)
Method Lookup in Multilevel Inheritance
How Python Finds Methods in the Chain
When a method is called on an object in multilevel inheritance, Python searches for the method starting from the child class and moving up the chain. The first method found is the one that's used. This is the Method Resolution Order (MRO).
# Method lookup in multilevel inheritance
class A:
def method(self):
return "A's method"
def common(self):
return "A's common method"
class B(A):
def method(self):
return "B's method"
class C(B):
def method(self):
return "C's method"
class D(C):
# No method defined here
pass
# Creating objects
d = D()
# Method lookup order:
# 1. Check D (doesn't have method)
# 2. Check C (has method)
print(d.method()) # C's method
# For common method:
# 1. Check D (doesn't have common)
# 2. Check C (doesn't have common)
# 3. Check B (doesn't have common)
# 4. Check A (has common)
print(d.common()) # A's common method
# Method lookup chain
print("Method lookup chain:")
for cls in D.__mro__:
print(f" {cls.__name__}")
# D → C → B → A → object
# Another example with method overriding
class Grandparent:
def message(self):
return "Message from Grandparent"
class Parent(Grandparent):
def message(self):
return "Message from Parent"
class Child(Parent):
def message(self):
return "Message from Child"
class Grandchild(Child):
pass
grandchild = Grandchild()
print(grandchild.message()) # Message from Child
# To call a specific version:
class SpecificChild(Child):
def all_messages(self):
return [
self.message(), # Child's version
super().message(), # Parent's version
super(Parent, self).message() # Grandparent's version
]
specific = SpecificChild()
print(specific.all_messages())
# ['Message from Child', 'Message from Parent', 'Message from Grandparent']
Method lookup key points:
- Starts at child — Python looks for the method in the child class first
- Moves up the chain — if not found, moves to parent, then grandparent
- First match wins — the first method found is used
- MRO defines order — Method Resolution Order determines the search path
- Can access ancestors — use super() to call specific versions
Quick Check: Where does Python first look for a method in multilevel inheritance? (Answer: In the child class)
super() in Multilevel Inheritance
Calling Ancestor Methods
In multilevel inheritance, super() follows the chain upward. It calls the next method in the MRO, allowing you to extend the behavior of ancestor classes.
# super() in multilevel inheritance
class Grandparent:
def __init__(self, name):
self.name = name
print(f"Grandparent __init__: {name}")
def work(self):
return f"{self.name} works"
class Parent(Grandparent):
def __init__(self, name, age):
super().__init__(name)
self.age = age
print(f"Parent __init__: {name}, {age}")
def work(self):
return f"{self.name} works and earns money"
class Child(Parent):
def __init__(self, name, age, school):
super().__init__(name, age)
self.school = school
print(f"Child __init__: {name}, {age}, {school}")
def work(self):
return f"{self.name} studies at {self.school}"
class Grandchild(Child):
def __init__(self, name, age, school, hobby):
super().__init__(name, age, school)
self.hobby = hobby
print(f"Grandchild __init__: {name}, {age}, {school}, {hobby}")
def work(self):
parent_work = super().work()
return f"{parent_work} and plays {self.hobby}"
# Creating a Grandchild
print("Creating Grandchild...")
grandchild = Grandchild("Alice", 10, "Python School", "coding")
print("\nWork:", grandchild.work())
# Alice studies at Python School and plays coding
# How super() works in the chain:
print("\nMethod chain with super():")
# Grandchild.work() calls:
# 1. super().work() → Child.work()
# 2. Child.work() → super().work() → Parent.work()
# 3. Parent.work() → super().work() → Grandparent.work()
# 4. Grandparent.work() returns
# The order of calls:
print("\nConstructor calls with super():")
# Grandchild.__init__ → Child.__init__ → Parent.__init__ → Grandparent.__init__
# Using super() to call specific ancestor methods
class Demo(Grandchild):
def show_all_work(self):
print("Direct call (self):", self.work())
print("super() call:", super().work())
print("super(Parent) call:", super(Parent, self).work())
print("super(Grandparent) call:", super(Grandparent, self).work())
demo = Demo("Bob", 12, "Math School", "chess")
print("\nAll work versions:")
demo.show_all_work()
super() in multilevel inheritance key points:
- Follows the chain — super() moves up the inheritance chain
- Calls next ancestor — calls the method in the next class in MRO
- Chain of calls — each level calls super() to continue the chain
- Can skip levels — super(Class, self) can call ancestors of a specific class
- Essential for constructors — super().__init__() initializes all ancestors
Quick Check: In multilevel inheritance, what does super() call? (Answer: The method in the next class up the inheritance chain)
Constructors in the Chain
Initializing All Levels
In multilevel inheritance, each class should call its parent's constructor using super().__init__(). This ensures that all levels of the hierarchy are properly initialized.
# Constructors in multilevel inheritance
class Base:
def __init__(self, value1):
self.value1 = value1
print(f"Base __init__: {value1}")
class Level1(Base):
def __init__(self, value1, value2):
super().__init__(value1)
self.value2 = value2
print(f"Level1 __init__: {value2}")
class Level2(Level1):
def __init__(self, value1, value2, value3):
super().__init__(value1, value2)
self.value3 = value3
print(f"Level2 __init__: {value3}")
class Level3(Level2):
def __init__(self, value1, value2, value3, value4):
super().__init__(value1, value2, value3)
self.value4 = value4
print(f"Level3 __init__: {value4}")
# Creating an object
print("Creating Level3 object...")
obj = Level3("A", "B", "C", "D")
# The constructor chain:
# Level3.__init__ → Level2.__init__ → Level1.__init__ → Base.__init__
print(f"\nobj.value1 = {obj.value1}")
print(f"obj.value2 = {obj.value2}")
print(f"obj.value3 = {obj.value3}")
print(f"obj.value4 = {obj.value4}")
# What happens if we forget super().__init__()?
class BrokenLevel(Level2):
def __init__(self, value1, value2, value3, value4):
# super().__init__(value1, value2, value3) # Missing!
self.value4 = value4
# This would cause issues because parent attributes aren't initialized
# Uncomment to see the error:
# broken = BrokenLevel("A", "B", "C", "D")
# print(broken.value1) # AttributeError!
# Proper way: Always call super().__init__()
class GoodLevel(Level2):
def __init__(self, value1, value2, value3, value4):
super().__init__(value1, value2, value3)
self.value4 = value4
good = GoodLevel("A", "B", "C", "D")
print(f"\nGoodLevel works: {good.value1}, {good.value2}, {good.value3}, {good.value4}")
Constructors key points:
- Call super().__init__() — always call the parent constructor
- Chain of initialization — constructors are called from child up to grandparent
- All attributes initialized — ensures all levels have their attributes set
- Don't skip — forgetting to call super().__init__() causes errors
- Order matters — parent attributes are initialized before child attributes
Quick Check: Why should you call super().__init__() in every child class? (Answer: To ensure all parent attributes are properly initialized)
Real-World Examples
Seeing Multilevel Inheritance in Action
# Real-world example: An Employee Management System
class Person:
"""Base class for all people"""
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
print(f"Person __init__: {name}")
def introduce(self):
return f"Hi, I'm {self.name}, {self.age} years old"
def get_address(self):
return f"{self.name} lives at {self.address}"
class Employee(Person):
"""A class representing an employee"""
def __init__(self, name, age, address, employee_id, department):
super().__init__(name, age, address)
self.employee_id = employee_id
self.department = department
self.salary = 0
print(f"Employee __init__: {name} ({employee_id})")
def work(self):
return f"{self.name} is working in {self.department}"
def set_salary(self, amount):
self.salary = amount
return f"{self.name}'s salary set to ${amount}"
class Manager(Employee):
"""A class representing a manager"""
def __init__(self, name, age, address, employee_id, department, team_size):
super().__init__(name, age, address, employee_id, department)
self.team_size = team_size
self.team_members = []
print(f"Manager __init__: {name} (team: {team_size})")
def work(self):
return f"{self.name} is managing a team of {self.team_size} people"
def add_team_member(self, member):
self.team_members.append(member)
return f"{member} added to {self.name}'s team"
class Executive(Manager):
"""A class representing an executive"""
def __init__(self, name, age, address, employee_id, department, team_size, executive_level):
super().__init__(name, age, address, employee_id, department, team_size)
self.executive_level = executive_level
self.stock_options = 0
print(f"Executive __init__: {name} (Level {executive_level})")
def work(self):
return f"{self.name} is leading the company as Level {self.executive_level} executive"
def grant_stock_options(self, amount):
self.stock_options = amount
return f"{self.name} granted {amount} stock options"
# Using the system
print("=== Creating an Executive ===\n")
executive = Executive(
name="Alice",
age=45,
address="123 Main St",
employee_id="E001",
department="Executive",
team_size=10,
executive_level="Senior"
)
print("\n=== Using Methods ===")
print(executive.introduce()) # Person method
print(executive.work()) # Executive method (overridden)
print(executive.set_salary(200000)) # Employee method
print(executive.add_team_member("Bob")) # Manager method
print(executive.grant_stock_options(1000)) # Executive method
print("\n=== Inheritance Chain ===")
print("Executive → Manager → Employee → Person → object")
for cls in Executive.__mro__:
print(f" {cls.__name__}")
Real-world example key points:
- Person — base class with common attributes
- Employee — adds work-related attributes
- Manager — adds management capabilities
- Executive — adds leadership and stock options
- Method overriding — each level customizes the work() method
Quick Check: What does each level in the Employee hierarchy add? (Answer: Each level adds more specific attributes and methods)
Best Practices for Multilevel Inheritance
Using Multilevel Inheritance Effectively
# Best practices for multilevel inheritance
# 1. Keep the chain shallow (2-3 levels)
# Good: 3 levels
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass
# Bad: Too many levels
class A: pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass
class F(E): pass
# 2. Use logical progression
# Good: Each level adds meaningful specialization
class Vehicle: pass
class Car(Vehicle): pass
class SportsCar(Car): pass
# Bad: Adding unrelated features
class Animal: pass
class Mammal(Animal): pass
class FlyingMammal(Mammal): pass # This might be better as a mixin
# 3. Always call super().__init__()
class Grandparent:
def __init__(self, value):
self.value = value
class Parent(Grandparent):
def __init__(self, value, extra):
super().__init__(value) # Good
self.extra = extra
class Child(Parent):
def __init__(self, value, extra, more):
super().__init__(value, extra) # Good
self.more = more
# 4. Use method overriding for specialization
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
# No need to override area - it works correctly
# 5. Document the inheritance chain
class Product:
"""
Base class for products.
Subclasses:
ElectronicProduct: Products with electronics
FoodProduct: Products that are food items
BookProduct: Products that are books
"""
pass
# 6. Use composition when inheritance doesn't fit
# If the relationship is "has-a" rather than "is-a"
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Composition, not inheritance
# 7. Avoid deep chains for simple cases
# For simple cases, inheritance might be overkill
class SimpleClass:
pass
# Instead of creating a chain of 3 classes for simple functionality
Best practices summary:
- Keep chains shallow — avoid deep inheritance hierarchies
- Logical progression — each level should add meaningful functionality
- Call super().__init__() — always initialize parent classes
- Override for specialization — use method overriding to customize behavior
- Document the chain — explain the inheritance relationship
- Prefer composition — when inheritance doesn't fit the relationship
Quick Check: How many levels should a multilevel inheritance chain have? (Answer: 2-3 levels maximum, keep it shallow)
Try It Yourself
Experiment with multilevel inheritance in the editor below.
MULTILEVEL INHERITANCE PRACTICE
========================================
1. BASIC MULTILEVEL INHERITANCE
Hello from Child
2. USING SUPER()
C.show() calls:
C
B
A
3. CONSTRUCTOR CHAIN
Z __init__
Y __init__
X __init__
4. CHECKING MRO
MRO for Z: ['Z', 'Y', 'X', 'object']
Multilevel inheritance practice complete!
You've Got It!
You now understand multilevel inheritance in Python. You know how classes inherit in a chain, how to use super(), and how to initialize all levels properly.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the difference between multilevel and multiple inheritance?
Can I have more than three levels in multilevel inheritance?
What happens if I don't call super().__init__()?
super().__init__(), the parent class's attributes won't be initialized. This can lead to AttributeError when you try to access parent attributes. Always call super().__init__() in the child class's constructor.
What's a common interview question about multilevel inheritance?
When should I use multilevel inheritance?
Can a child class access the grandparent's methods directly?
Where to Go From Here
Now that you understand multilevel inheritance, check out these related topics:
Hierarchical Inheritance
Learn about multiple children from one parent.
Learn More →Hybrid Inheritance
Learn about combining multiple and multilevel inheritance.
Learn More →Method Overriding
Learn more about overriding methods in subclasses.
Learn More →