Table of Contents
- Understanding Inheritance Basics
- Multilevel Inheritance
- 2.1 Definition & Syntax
- 2.2 Example: Building a Class Hierarchy
- 2.3 Pros & Cons
- Multiple Inheritance
- 3.1 Definition & Syntax
- 3.2 The Diamond Problem
- Method Resolution Order (MRO)
- 4.1 What is MRO?
- 4.2 How to View MRO
- 4.3 C3 Linearization Algorithm
- Practical Examples
- 5.1 Multilevel Inheritance: User Roles in a System
- 5.2 Multiple Inheritance: A Smart Device
- Best Practices
- Common Pitfalls
- Conclusion
- References
1. Understanding Inheritance Basics
Before diving into advanced forms, let’s recap single inheritance—the foundation of OOP inheritance. In single inheritance, a derived class inherits from one base class.
Syntax:
class BaseClass:
def base_method(self):
print("This is a base class method.")
class DerivedClass(BaseClass):
def derived_method(self):
print("This is a derived class method.")
Key Terms:
- Base Class (Superclass): The class being inherited from (e.g.,
BaseClass). - Derived Class (Subclass): The class that inherits (e.g.,
DerivedClass). super(): A built-in function to call methods from the base class (avoids hardcoding the base class name).
Example Usage:
obj = DerivedClass()
obj.base_method() # Inherited from BaseClass: "This is a base class method."
obj.derived_method() # Defined in DerivedClass: "This is a derived class method."
Single inheritance is straightforward, but real-world systems often require more flexibility—hence multilevel and multiple inheritance.
2. Multilevel Inheritance
2.1 Definition & Syntax
Multilevel inheritance occurs when a derived class inherits from another derived class, forming a chain of inheritance. For example:
Class A → Class B (inherits A) → Class C (inherits B)
Here, C inherits from B, which in turn inherits from A. Thus, C gains attributes/methods from both B and A.
Syntax:
class Level1: # Base class (Level 1)
def method1(self):
print("Level 1 method.")
class Level2(Level1): # Inherits from Level1 (Level 2)
def method2(self):
print("Level 2 method.")
class Level3(Level2): # Inherits from Level2 (Level 3)
def method3(self):
print("Level 3 method.")
2.2 Example: Building a Class Hierarchy
Let’s model a biological hierarchy: Animal → Mammal → Dog.
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
class Mammal(Animal): # Inherits from Animal
def give_birth(self):
print(f"{self.name} gives live birth.")
class Dog(Mammal): # Inherits from Mammal (and thus Animal)
def bark(self):
print(f"{self.name} says: Woof!")
# Usage
my_dog = Dog("Buddy")
my_dog.eat() # Inherited from Animal: "Buddy is eating."
my_dog.give_birth() # Inherited from Mammal: "Buddy gives live birth."
my_dog.bark() # Defined in Dog: "Buddy says: Woof!"
Here, Dog inherits __init__ and eat() from Animal, give_birth() from Mammal, and adds its own bark().
2.3 Pros & Cons
Pros:
- Promotes code reuse across a logical hierarchy (e.g., biological classifications, user roles).
- Simplifies maintenance: Changes to a base class propagate to all derived classes.
Cons:
- Tight coupling: Deep hierarchies (e.g., 5+ levels) make code hard to debug. A change in a top-level class can break many subclasses.
- Reduced flexibility: Subclasses are locked into the hierarchy’s structure.
3. Multiple Inheritance
3.1 Definition & Syntax
Multiple inheritance allows a derived class to inherit from two or more base classes. This is powerful for combining features from unrelated classes.
Syntax:
class Parent1:
def method1(self):
print("Parent1 method.")
class Parent2:
def method2(self):
print("Parent2 method.")
class Child(Parent1, Parent2): # Inherits from both Parent1 and Parent2
def method3(self):
print("Child method.")
Example:
child = Child()
child.method1() # From Parent1: "Parent1 method."
child.method2() # From Parent2: "Parent2 method."
child.method3() # From Child: "Child method."
3.2 The Diamond Problem
A classic challenge in multiple inheritance is the diamond problem, where a class inherits from two classes that share a common base class. For example:
A
/ \
B C
\ /
D
Here, D inherits from B and C, which both inherit from A. If A, B, and C all have a method foo(), which version does D use?
Python resolves this with the Method Resolution Order (MRO)—a strict order for searching methods in base classes.
4. Method Resolution Order (MRO)
4.1 What is MRO?
MRO is the order in which Python searches for methods and attributes in the base classes of a derived class. It ensures consistency when multiple inheritance leads to overlapping method names.
4.2 How to View MRO
Use the __mro__ attribute or mro() method to inspect the order:
print(Child.__mro__) # Returns a tuple of classes in resolution order
# OR
print(Child.mro()) # Returns a list of classes in resolution order
4.3 C3 Linearization Algorithm
Python uses the C3 linearization algorithm to compute MRO. Key rules:
- Local precedence: A class comes before its base classes.
- Monotonicity: If class
Aprecedes classBin the MRO of a base class,Awill precedeBin the derived class’s MRO. - No duplicates: Each class appears exactly once.
Example: Diamond Problem Resolved
Let’s code the diamond problem and check MRO:
class A:
def foo(self):
print("A's foo")
class B(A):
def foo(self):
print("B's foo")
super().foo() # Calls next in MRO
class C(A):
def foo(self):
print("C's foo")
super().foo() # Calls next in MRO
class D(B, C):
pass
# View MRO of D
print(D.mro())
# Output: [D, B, C, A, object]
MRO for D is [D, B, C, A, object]. When D().foo() is called:
Dhas nofoo(), so Python checksB.B.foo()runs and callssuper().foo(), which follows MRO toC.C.foo()runs and callssuper().foo(), which follows MRO toA.A.foo()runs, thensuper().foo()(no more classes, so stops).
Output:
B's foo
C's foo
A's foo
MRO ensures B comes before C, and A comes last—resolving the diamond problem!
5. Practical Examples
5.1 Multilevel Inheritance: User Roles in a System
Imagine a user authentication system with hierarchical roles:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def view_profile(self):
print(f"Name: {self.name}, Email: {self.email}")
class Admin(User):
def delete_user(self, user):
print(f"Admin {self.name} deleted user {user.name}")
class SuperAdmin(Admin):
def delete_admin(self, admin):
print(f"SuperAdmin {self.name} deleted admin {admin.name}")
# Usage
super_admin = SuperAdmin("Alice", "[email protected]")
super_admin.view_profile() # Inherited from User
super_admin.delete_user(User("Bob", "[email protected]")) # Inherited from Admin
super_admin.delete_admin(Admin("Charlie", "[email protected]")) # Defined in SuperAdmin
Here, SuperAdmin inherits from Admin, which inherits from User—a clean multilevel hierarchy.
5.2 Multiple Inheritance: A Smart Device
A Smartphone combines features of a Phone and a Computer:
class Phone:
def call(self, number):
print(f"Calling {number}...")
class Computer:
def browse(self, url):
print(f"Browsing {url}...")
class Smartphone(Phone, Computer):
def take_photo(self):
print("Photo taken!")
# Usage
my_phone = Smartphone()
my_phone.call("+123456789") # From Phone
my_phone.browse("https://python.org") # From Computer
my_phone.take_photo() # From Smartphone
MRO for Smartphone is [Smartphone, Phone, Computer, object], so methods from Phone take precedence over Computer if they overlap.
6. Best Practices
To use multilevel and multiple inheritance effectively:
- Avoid deep hierarchies: Limit multilevel inheritance to 2–3 levels (e.g.,
A → B → C). Deeper chains (e.g.,A→B→C→D→E) become unmaintainable. - Prefer composition over inheritance: Use composition (e.g., class
Ahas an instance of classB) for loose coupling instead of inheritance when possible. - Explicit
super()calls: Usesuper()to call base class methods, especially in multiple inheritance. Avoid hardcoding base class names (e.g.,Parent1.method(self)). - Document MRO: For complex multiple inheritance, document the MRO to clarify method resolution for other developers.
- Use ABCs for interfaces: Enforce method contracts with
abc.ABCto ensure base classes define required methods (reduces bugs in derived classes).
7. Common Pitfalls
- Accidental method overriding: A derived class may unknowingly override a base class method, breaking functionality.
super()confusion: Incorrect use ofsuper()(e.g., omitting it) can skip critical base class methods in MRO.- Diamond problem surprises: Even with MRO, unexpected behavior can occur if base classes have overlapping methods with side effects.
- Tight coupling: Multilevel inheritance creates tight dependencies—changes to a top-level class can break all subclasses.
8. Conclusion
Multilevel and multiple inheritance are powerful tools in Python’s OOP toolkit, enabling code reuse and flexible class design. Multilevel inheritance excels in hierarchical systems (e.g., user roles), while multiple inheritance shines when combining unrelated features (e.g., a device with phone and computer capabilities).
The key to mastering these patterns is understanding MRO, which resolves method conflicts in multiple inheritance. By following best practices—limiting hierarchy depth, using super() explicitly, and preferring composition—you can avoid common pitfalls and build maintainable systems.
9. References
- Python Official Documentation: Inheritance
- Python MRO Documentation
- C3 Linearization Algorithm
- Fluent Python by Luciano Ramalho (Chapter 12: Inheritance: For Good or For Worse)
- Python Crash Course by Eric Matthes (Chapter 9: Classes)