- What is multiple inheritance — inheriting from multiple parents
- Syntax — how to create classes with multiple parents
- MRO — Method Resolution Order explained
- The diamond problem — understanding the challenge
- Mixins — a practical use case
- Best practices — using multiple inheritance effectively
What is Multiple Inheritance?
Multiple inheritance is a feature where a class can inherit from more than one parent class. This allows a class to combine the functionality of multiple classes into one. It's like having two parents — you inherit traits from both.
Think of it like a hybrid vehicle that combines the features of both an electric car and a gasoline car. The hybrid inherits from both types, getting the best of both worlds.
Multiple inheritance is powerful but also complex. It can lead to ambiguity when the same method exists in multiple parents. Python handles this using the Method Resolution Order (MRO).
💡 Key concept: Multiple inheritance allows a class to inherit from multiple parent classes. It's powerful but requires careful design to avoid conflicts.
Syntax of Multiple Inheritance
Creating Classes with Multiple Parents
The syntax for multiple inheritance is simple: you list the parent classes in parentheses, separated by commas. The child class inherits from all of them.
# Syntax of multiple inheritance
# Two parent classes
class Flyable:
"""A class representing flying ability"""
def __init__(self):
self.altitude = 0
def fly(self):
self.altitude = 100
return f"Flying at {self.altitude} feet"
class Swimmable:
"""A class representing swimming ability"""
def __init__(self):
self.depth = 0
def swim(self):
self.depth = 10
return f"Swimming at {self.depth} meters"
# Child class inheriting from both parents
class Duck(Flyable, Swimmable):
"""A class representing a duck that can both fly and swim"""
def __init__(self, name):
# Call both parent constructors
Flyable.__init__(self)
Swimmable.__init__(self)
self.name = name
def quack(self):
return f"{self.name} says Quack!"
# Creating a duck
duck = Duck("Donald")
# Using methods from Flyable
print(duck.fly()) # Flying at 100 feet
# Using methods from Swimmable
print(duck.swim()) # Swimming at 10 meters
# Using its own method
print(duck.quack()) # Donald says Quack!
# Checking the inheritance
print(f"Is Duck a Flyable? {issubclass(Duck, Flyable)}") # True
print(f"Is Duck a Swimmable? {issubclass(Duck, Swimmable)}") # True
Multiple inheritance syntax:
- Syntax — class Child(Parent1, Parent2):
- Multiple parents — child inherits from all listed classes
- Constructor calls — need to call each parent's __init__
- Method lookup — follows the Method Resolution Order
- Flexibility — combines functionality from different sources
Quick Check: How do you write a class that inherits from two parents? (Answer: class Child(Parent1, Parent2):)
Method Resolution Order (MRO)
Understanding How Python Resolves Methods
The Method Resolution Order (MRO) determines the order in which Python searches for methods in a class hierarchy. In multiple inheritance, the MRO becomes especially important.
# Understanding MRO in multiple inheritance
class A:
def method(self):
return "A's method"
def common(self):
return "A's common method"
class B:
def method(self):
return "B's method"
def common(self):
return "B's common method"
class C(A, B):
pass
class D(B, A):
pass
# Creating objects
c = C()
d = D()
# Method resolution
print("C.method():", c.method()) # A's method (A comes before B)
print("D.method():", d.method()) # B's method (B comes before A)
# Common method resolution
print("C.common():", c.common()) # A's common method
print("D.common():", d.common()) # B's common method
# Checking MRO
print("\nMRO for C:")
for cls in C.__mro__:
print(f" {cls.__name__}")
print("\nMRO for D:")
for cls in D.__mro__:
print(f" {cls.__name__}")
# The MRO determines the order of method lookup
# Python uses the C3 linearization algorithm
# Another example with more complex hierarchy
class X:
def method(self):
return "X's method"
class Y:
def method(self):
return "Y's method"
class Z(X, Y):
pass
class W(Y, X):
pass
class Final(Z, W):
pass
print("\nFinal.method():", Final().method()) # X's method
print("MRO for Final:")
for cls in Final.__mro__:
print(f" {cls.__name__}")
# Final → Z → X → W → Y → object
MRO key points:
- Defines order — the order in which classes are searched
- C3 algorithm — Python uses the C3 linearization algorithm
- Consistent — maintains a consistent order across the hierarchy
- Check with __mro__ — use Class.__mro__ to see the order
- Affects method lookup — determines which method is called
Quick Check: What does MRO stand for? (Answer: Method Resolution Order)
The Diamond Problem
Understanding a Classic Challenge
The diamond problem occurs in multiple inheritance when a class inherits from two classes that have a common ancestor. This creates a diamond-shaped inheritance hierarchy and can lead to ambiguity.
# The diamond problem
class A:
"""The base class"""
def __init__(self):
print("A __init__")
self.value = "A"
def show(self):
return f"A's value: {self.value}"
class B(A):
"""Inherits from A"""
def __init__(self):
print("B __init__")
super().__init__()
self.value = "B"
class C(A):
"""Inherits from A"""
def __init__(self):
print("C __init__")
super().__init__()
self.value = "C"
class D(B, C):
"""Inherits from both B and C (diamond shape)"""
def __init__(self):
print("D __init__")
super().__init__()
# What value will we get?
# Creating D
print("Creating D (diamond):")
d = D()
# What will the value be?
print(d.show()) # What does this print?
print(f"Value: {d.value}")
# Let's see the MRO
print("\nMRO for D:")
for cls in D.__mro__:
print(f" {cls.__name__}")
# Python resolves the diamond problem using MRO
# The MRO determines which __init__ is called and in what order
# In this case: D → B → C → A
# Another example showing the diamond problem
class Animal:
def speak(self):
return "Animal sound"
class Mammal(Animal):
def speak(self):
return "Mammal sound"
class Bird(Animal):
def speak(self):
return "Bird sound"
class Bat(Mammal, Bird):
pass
bat = Bat()
print(bat.speak()) # Mammal sound (Mammal comes before Bird in MRO)
print("MRO for Bat:", [cls.__name__ for cls in Bat.__mro__])
# Bat → Mammal → Bird → Animal → object
Diamond problem key points:
- Diamond shape — occurs when two classes inherit from the same parent
- Ambiguity — which parent's method should be called?
- MRO solves it — Python uses the MRO to determine the order
- super() works — super() follows the MRO
- Can be confusing — careful design is needed
Quick Check: What is the diamond problem? (Answer: When a class inherits from two classes that have a common ancestor, creating ambiguity)
Using super() in Multiple Inheritance
How super() Works with Multiple Parents
In multiple inheritance, super() follows the MRO. This means that super() calls the next class in the MRO, not necessarily the immediate parent. This is important for correctly initializing all parent classes.
# Using super() in multiple inheritance
class A:
def __init__(self):
print("A __init__ called")
self.value_a = "A"
def show(self):
return f"A's value: {self.value_a}"
class B:
def __init__(self):
print("B __init__ called")
self.value_b = "B"
def show(self):
return f"B's value: {self.value_b}"
class C(A, B):
def __init__(self):
print("C __init__ called")
super().__init__()
# super() calls A.__init__ (since A is first in MRO)
def show(self):
return f"C's values: {self.value_a}, {self.value_b}"
class D(B, A):
def __init__(self):
print("D __init__ called")
super().__init__()
# super() calls B.__init__ (since B is first in MRO)
def show(self):
return f"D's values: {self.value_a}, {self.value_b}"
# Creating C
print("Creating C:")
c = C()
print(c.show()) # C's values: A, B
# Creating D
print("\nCreating D:")
d = D()
print(d.show()) # D's values: A, B
# MRO determines which __init__ is called first
print("\nMRO for C:", [cls.__name__ for cls in C.__mro__])
# C → A → B → object
print("MRO for D:", [cls.__name__ for cls in D.__mro__])
# D → B → A → object
# Real-world example: A class combining multiple features
class LoggingMixin:
def log(self, message):
print(f"LOG: {message}")
if hasattr(self, 'name'):
print(f" From: {self.name}")
class SaveMixin:
def save(self):
print(f"SAVE: Saving data for {getattr(self, 'name', 'unknown')}")
class User(LoggingMixin, SaveMixin):
def __init__(self, name):
self.name = name
def process(self):
self.log("Processing started")
self.save()
self.log("Processing completed")
user = User("Alice")
user.process()
# LOG: Processing started
# From: Alice
# SAVE: Saving data for Alice
# LOG: Processing completed
# From: Alice
super() in multiple inheritance:
- Follows MRO — calls the next class in the MRO
- Works through chain — super() calls propagate through the MRO
- All parents initialized — proper use of super() initializes all parents
- Mixin pattern — super() is essential for mixins
- Consistent — super() works the same way regardless of hierarchy
Quick Check: In multiple inheritance, what does super() call? (Answer: The next class in the MRO)
Mixins: A Practical Use Case
Reusable Components with Mixins
Mixins are classes that provide specific functionality that can be mixed into other classes. They're a practical use of multiple inheritance, allowing you to add features to classes without copying code.
# Mixins in Python
# A mixin is a class that adds specific functionality
class TimestampMixin:
"""A mixin that adds timestamp functionality"""
def get_timestamp(self):
import datetime
return datetime.datetime.now().isoformat()
def log_with_timestamp(self, message):
timestamp = self.get_timestamp()
print(f"[{timestamp}] {message}")
class JSONSerializableMixin:
"""A mixin that adds JSON serialization"""
def to_json(self):
import json
if hasattr(self, '__dict__'):
return json.dumps(self.__dict__)
return json.dumps({})
def from_json(self, json_string):
import json
data = json.loads(json_string)
if hasattr(self, '__dict__'):
self.__dict__.update(data)
return self
class SaveableMixin:
"""A mixin that adds save/load functionality"""
def save_to_file(self, filename):
with open(filename, 'w') as f:
if hasattr(self, 'to_json'):
f.write(self.to_json())
else:
f.write(str(self.__dict__))
return f"Saved to {filename}"
# Using mixins with a class
class User(TimestampMixin, JSONSerializableMixin, SaveableMixin):
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"Hi, I'm {self.name}, {self.age} years old"
# Using the class with mixins
user = User("Alice", 25)
# Using TimestampMixin
user.log_with_timestamp("User created") # [2026-08-09T...] User created
# Using JSONSerializableMixin
json_data = user.to_json()
print(f"JSON: {json_data}") # JSON: {"name": "Alice", "age": 25}
# Using SaveableMixin
user.save_to_file("user.json") # Saved to user.json
# Another example: A mixin for validation
class ValidatableMixin:
def validate(self):
# Generic validation that can be overridden
return True
def validate_and_raise(self):
if not self.validate():
raise ValueError(f"Validation failed for {self.__class__.__name__}")
class Product(ValidatableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
def validate(self):
return self.price > 0 and len(self.name) > 0
# Using the validation mixin
product = Product("Laptop", 999.99)
print(product.validate()) # True
# product.validate_and_raise() # Would raise an error if invalid
# Mixins are powerful because they:
# 1. Provide reusable functionality
# 2. Can be combined in any order
# 3. Keep classes focused
# 4. Avoid code duplication
Mixins key points:
- Add specific functionality — mixins provide focused features
- Reusable — mixins can be used in many classes
- Combine freely — multiple mixins can be combined
- Keep classes focused — mixins separate cross-cutting concerns
- Name convention — mixin class names often end in "Mixin"
Quick Check: What is a mixin? (Answer: A class that provides specific, reusable functionality to be mixed into other classes)
Challenges and Solutions
Overcoming Common Issues
# Challenges with multiple inheritance and how to solve them
# Challenge 1: Method name conflicts
class A:
def process(self):
return "A's process"
class B:
def process(self):
return "B's process"
class C(A, B):
pass
# The MRO determines which method is called
c = C()
print(c.process()) # A's process (A comes first)
# Solution: Explicitly call the desired parent's method
class D(A, B):
def process(self):
return B.process(self) # Explicitly call B's method
d = D()
print(d.process()) # B's process
# Challenge 2: Constructor conflicts
class A:
def __init__(self, a_value):
self.a = a_value
print(f"A initialized with {a_value}")
class B:
def __init__(self, b_value):
self.b = b_value
print(f"B initialized with {b_value}")
# Solution: Use *args and **kwargs
class C(A, B):
def __init__(self, a_value, b_value, c_value):
A.__init__(self, a_value)
B.__init__(self, b_value)
self.c = c_value
print(f"C initialized with {c_value}")
c = C("A", "B", "C")
print(f"c.a={c.a}, c.b={c.b}, c.c={c.c}")
# Challenge 3: Diamond problem with constructors
class Base:
def __init__(self):
self.base_value = "Base"
print("Base __init__")
class X(Base):
def __init__(self):
super().__init__()
self.x_value = "X"
print("X __init__")
class Y(Base):
def __init__(self):
super().__init__()
self.y_value = "Y"
print("Y __init__")
class Z(X, Y):
def __init__(self):
super().__init__()
self.z_value = "Z"
print("Z __init__")
z = Z()
print(f"z.base_value={z.base_value}, z.x_value={z.x_value}, z.y_value={z.y_value}, z.z_value={z.z_value}")
# Solution: super() handles the diamond correctly
# The MRO ensures each constructor is called once
print("MRO for Z:", [cls.__name__ for cls in Z.__mro__])
# Z → X → Y → Base → object
Common challenges and solutions:
- Method conflicts — use MRO or explicitly call parent methods
- Constructor conflicts — use *args/**kwargs or explicitly call each parent
- Diamond problem — super() handles it correctly following MRO
- Complexity — keep hierarchies shallow and use mixins
- Testing — test each parent independently and the combination
Quick Check: How can you resolve method name conflicts in multiple inheritance? (Answer: Use the MRO or explicitly call the desired parent's method)
Best Practices for Multiple Inheritance
Using Multiple Inheritance Wisely
# Best practices for multiple inheritance
# 1. Use mixins for specific functionality
class LoggingMixin:
def log(self, message):
print(f"LOG: {message}")
class SerializableMixin:
def to_dict(self):
return self.__dict__
class User(LoggingMixin, SerializableMixin):
def __init__(self, name):
self.name = name
# 2. Keep class hierarchies shallow
# Good: 2-3 levels
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# Bad: Very deep hierarchy
# class A: pass
# class B(A): pass
# class C(B): pass
# class D(C): pass
# class E(D): pass
# class F(E): pass
# 3. Use super() consistently
class A:
def __init__(self):
print("A")
super().__init__()
class B:
def __init__(self):
print("B")
super().__init__()
class C(A, B):
def __init__(self):
print("C")
super().__init__()
# 4. Avoid inheriting from classes with conflicting interfaces
# Good: Mixins with clear, non-conflicting methods
class ReadableMixin:
def read(self):
pass
class WritableMixin:
def write(self):
pass
# Bad: Classes with same method names doing different things
class SaveableA:
def save(self):
pass
class SaveableB:
def save(self):
pass
# 5. Document the inheritance relationship
class Employee:
"""
Base class for employees.
Mixins used:
LoggingMixin: Provides logging capabilities
TimestampMixin: Provides timestamp functionality
"""
pass
# 6. Use composition when inheritance doesn't fit
# If two classes don't have an "is-a" relationship, use composition
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Composition
def start(self):
return self.engine.start()
Best practices summary:
- Use mixins — for specific, reusable functionality
- Keep hierarchies shallow — avoid deep inheritance chains
- Use super() consistently — ensures proper initialization
- Avoid conflicts — ensure parent classes have compatible interfaces
- Document relationships — explain the inheritance structure
- Prefer composition — when inheritance doesn't fit the relationship
Quick Check: When should you use multiple inheritance? (Answer: When you need to combine functionality from multiple sources, especially for mixins)
Try It Yourself
Experiment with multiple inheritance in the editor below.
MULTIPLE INHERITANCE PRACTICE
========================================
1. BASIC MULTIPLE INHERITANCE
C.method(): A's method
2. CHECKING MRO
MRO for C: ['C', 'A', 'B', 'object']
3. USING SUPER()
Z __init__
X __init__
4. MIXIN EXAMPLE
[12:00:00] App is running
Multiple inheritance practice complete!
You've Got It!
You now understand multiple inheritance in Python. You know how to create classes with multiple parents, understand MRO, and use mixins effectively.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the difference between single and multiple inheritance?
What is the diamond problem in Python?
What are mixins and why are they useful?
What's a common interview question about multiple inheritance?
Can a class inherit from more than two parents?
class Child(Parent1, Parent2, Parent3, Parent4): is valid. However, more parents mean more complexity, so it's best to keep it manageable.
Is multiple inheritance better than single inheritance?
Where to Go From Here
Now that you understand multiple inheritance, check out these related topics:
Multilevel Inheritance
Learn about chains of inheritance.
Learn More →Hierarchical Inheritance
Learn about multiple children from one parent.
Learn More →Method Overriding
Learn more about overriding methods in subclasses.
Learn More →