🔀 Multiple Inheritance¶
📺 Video Tutorial¶
Learn Python multiple inheritance in 8 minutes! 🐟 (8:12)
What You’ll Learn¶
In this chapter, you’ll explore multiple inheritance - a powerful but complex OOP feature where a class can inherit from more than one parent class. You’ll learn how Python’s Method Resolution Order (MRO) determines which parent’s method gets called, understand the diamond problem, implement mixins for code reuse, and know when to choose multiple inheritance versus composition.
Learning Objectives¶
Create classes that inherit from multiple parent classes
Understand Method Resolution Order (MRO) and the C3 linearization algorithm
Recognize and handle the diamond problem
Implement mixin classes for adding functionality
Use
super()correctly in multiple inheritance scenariosDecide when multiple inheritance is appropriate vs composition
Concept Explanation¶
What is Multiple Inheritance?¶
Multiple Inheritance allows a class to inherit from more than one parent class.
# Single inheritance (normal)
class Child(Parent):
pass
# Multiple inheritance
class Child(Parent1, Parent2, Parent3):
pass
Real-world analogy:
A smartphone is both a phone AND a camera AND a music player - it has features from multiple “parent” devices.
Basic Multiple Inheritance Syntax¶
class Flyer:
def fly(self):
print("Flying in the air")
class Swimmer:
def swim(self):
print("Swimming in water")
class Duck(Flyer, Swimmer): # Inherits from both!
pass
duck = Duck()
duck.fly() # From Flyer
duck.swim() # From Swimmer
Method Resolution Order (MRO)¶
MRO determines which parent’s method gets called when there are conflicts.
Python uses C3 Linearization to create a predictable order.
class A:
def method(self):
print("A's method")
class B:
def method(self):
print("B's method")
class C(A, B): # A comes first
pass
c = C()
c.method() # A's method (A is checked first)
# View MRO
print(C.mro())
# [<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
MRO Search Order:
The class itself
First parent (left to right)
Second parent (left to right)
… and so on
The
objectclass (all classes inherit from object)
The Diamond Problem¶
Diamond Problem occurs when a class inherits from two classes that both inherit from a common parent.
Animal
/ \
Flyer Swimmer
\ /
Duck
class Animal:
def __init__(self):
print("Animal init")
class Flyer(Animal):
def __init__(self):
print("Flyer init")
super().__init__()
class Swimmer(Animal):
def __init__(self):
print("Swimmer init")
super().__init__()
class Duck(Flyer, Swimmer):
def __init__(self):
print("Duck init")
super().__init__()
duck = Duck()
# Output:
# Duck init
# Flyer init
# Swimmer init
# Animal init (only called ONCE!)
# MRO ensures Animal.__init__ is called only once
print(Duck.mro())
# [Duck, Flyer, Swimmer, Animal, object]
Key Point: Python’s MRO ensures each class’s __init__ is called only once, even in diamond inheritance!
Using super() with Multiple Inheritance¶
ALWAYS use super() instead of calling parent classes directly:
❌ Wrong:
class Duck(Flyer, Swimmer):
def __init__(self):
Flyer.__init__(self) # Might call Animal twice!
Swimmer.__init__(self)
✅ Correct:
class Duck(Flyer, Swimmer):
def __init__(self):
super().__init__() # Follows MRO correctly
Mixins¶
Mixin = A class designed to add specific functionality through multiple inheritance.
Mixins usually:
Don’t have their own
__init__Provide specific methods
Are combined with other classes
class JSONMixin:
"""Add JSON export capability."""
def to_json(self):
import json
return json.dumps(self.__dict__)
class XMLMixin:
"""Add XML export capability."""
def to_xml(self):
items = ''.join(f'<{k}>{v}</{k}>' for k, v in self.__dict__.items())
return f'<object>{items}</object>'
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# Add JSON and XML export to Person
class ExportablePerson(Person, JSONMixin, XMLMixin):
pass
person = ExportablePerson("Alice", 30)
print(person.to_json()) # {"name": "Alice", "age": 30}
print(person.to_xml()) # <object><name>Alice</name><age>30</age></object>
When to Use Multiple Inheritance¶
✅ Good use cases:
Mixins for adding features (logging, serialization)
Interface-like behavior (Flyable, Swimmable)
Small, well-defined capabilities
❌ Avoid when:
Creates complex diamond hierarchies
Parents have conflicting methods
Composition would be clearer
Alternative: Composition
# Instead of: class Duck(Flyer, Swimmer)
class Duck:
def __init__(self):
self.flight = Flyer() # Has-a relationship
self.swimming = Swimmer()
def fly(self):
self.flight.fly()
def swim(self):
self.swimming.swim()
Examples¶
Example 1: Basic Multiple Inheritance¶
class Animal:
"""Base animal class."""
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating")
class Flyer:
"""Flying capability."""
def fly(self):
print(f"{self.name} is flying")
class Swimmer:
"""Swimming capability."""
def swim(self):
print(f"{self.name} is swimming")
# Duck can do both!
class Duck(Animal, Flyer, Swimmer):
pass
# Fish can only swim
class Fish(Animal, Swimmer):
pass
# Bird can only fly
class Bird(Animal, Flyer):
pass
# Test
duck = Duck("Donald")
duck.eat() # From Animal
duck.fly() # From Flyer
duck.swim() # From Swimmer
fish = Fish("Nemo")
fish.eat()
fish.swim()
# fish.fly() # ERROR - Fish can't fly
print(f"\nDuck MRO: {[cls.__name__ for cls in Duck.mro()]}")
# Duck MRO: ['Duck', 'Animal', 'Flyer', 'Swimmer', 'object']
Example 2: Mixin Classes for Serialization¶
import json
import pickle
class Person:
"""Basic person class."""
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"Person('{self.name}', {self.age}, '{self.email}')"
class JSONSerializableMixin:
"""Add JSON serialization."""
def to_json(self):
"""Convert to JSON string."""
return json.dumps(self.__dict__, indent=2)
@classmethod
def from_json(cls, json_str):
"""Create instance from JSON string."""
data = json.loads(json_str)
return cls(**data)
class PickleSerializableMixin:
"""Add pickle serialization."""
def to_pickle(self):
"""Convert to pickle bytes."""
return pickle.dumps(self)
@classmethod
def from_pickle(cls, pickle_bytes):
"""Create instance from pickle bytes."""
return pickle.loads(pickle_bytes)
class DictMixin:
"""Add dictionary conversion."""
def to_dict(self):
"""Convert to dictionary."""
return self.__dict__.copy()
def update_from_dict(self, data):
"""Update attributes from dictionary."""
for key, value in data.items():
setattr(self, key, value)
# Combine Person with all mixins
class SerializablePerson(Person, JSONSerializableMixin,
PickleSerializableMixin, DictMixin):
"""Person with serialization capabilities."""
pass
# Test all serialization methods
person = SerializablePerson("Alice", 30, "alice@email.com")
# JSON
json_str = person.to_json()
print("JSON:")
print(json_str)
# Dictionary
person_dict = person.to_dict()
print("\nDictionary:")
print(person_dict)
# Pickle
pickle_bytes = person.to_pickle()
print(f"\nPickle: {len(pickle_bytes)} bytes")
# Restore from JSON
restored = SerializablePerson.from_json(json_str)
print(f"\nRestored from JSON: {restored}")
Example 3: Diamond Inheritance Pattern¶
class Vehicle:
"""Base vehicle class."""
def __init__(self, name):
self.name = name
print(f"Vehicle.__init__({name})")
def move(self):
print(f"{self.name} is moving")
class LandVehicle(Vehicle):
"""Land-based vehicle."""
def __init__(self, name, wheels):
print(f"LandVehicle.__init__({name}, {wheels})")
super().__init__(name)
self.wheels = wheels
def drive(self):
print(f"{self.name} is driving on {self.wheels} wheels")
class WaterVehicle(Vehicle):
"""Water-based vehicle."""
def __init__(self, name, displacement):
print(f"WaterVehicle.__init__({name}, {displacement})")
super().__init__(name)
self.displacement = displacement
def sail(self):
print(f"{self.name} is sailing ({self.displacement} tons)")
class AmphibiousVehicle(LandVehicle, WaterVehicle):
"""Can operate on land and water."""
def __init__(self, name, wheels, displacement):
print(f"AmphibiousVehicle.__init__({name})")
LandVehicle.__init__(self, name, wheels)
WaterVehicle.__init__(self, name, displacement)
def transform(self, mode):
"""Switch between land and water mode."""
if mode == "land":
print(f"{self.name} transforming to land mode")
self.drive()
elif mode == "water":
print(f"{self.name} transforming to water mode")
self.sail()
# Create amphibious vehicle
amphibious = AmphibiousVehicle("Duck Boat", 4, 5)
print("\n--- Testing ---")
amphibious.move()
amphibious.transform("land")
amphibious.transform("water")
print(f"\n--- MRO ---")
for cls in AmphibiousVehicle.mro():
print(cls.__name__)
Example 4: Logging and Validation Mixins¶
from datetime import datetime
class LoggingMixin:
"""Add logging capability."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logs = []
def log(self, message):
"""Add log entry."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
entry = f"[{timestamp}] {message}"
self.logs.append(entry)
print(entry)
def show_logs(self):
"""Display all logs."""
print("\n=== Logs ===")
for log in self.logs:
print(log)
class ValidationMixin:
"""Add validation capability."""
def validate_positive(self, value, name):
"""Ensure value is positive."""
if value <= 0:
raise ValueError(f"{name} must be positive")
def validate_string(self, value, name):
"""Ensure value is non-empty string."""
if not isinstance(value, str) or not value.strip():
raise ValueError(f"{name} must be a non-empty string")
def validate_range(self, value, name, min_val, max_val):
"""Ensure value is in range."""
if not min_val <= value <= max_val:
raise ValueError(f"{name} must be between {min_val} and {max_val}")
class BankAccount:
"""Basic bank account."""
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
class SecureBankAccount(BankAccount, LoggingMixin, ValidationMixin):
"""Bank account with logging and validation."""
def __init__(self, owner, balance=0):
# Validate inputs
self.validate_string(owner, "Owner name")
self.validate_positive(balance if balance > 0 else 1, "Initial balance")
# Initialize parent classes
super().__init__(owner, balance)
# Log creation
self.log(f"Account created for {owner} with balance ${balance}")
def deposit(self, amount):
"""Deposit with validation and logging."""
self.validate_positive(amount, "Deposit amount")
self.balance += amount
self.log(f"Deposited ${amount}. New balance: ${self.balance}")
def withdraw(self, amount):
"""Withdraw with validation and logging."""
self.validate_positive(amount, "Withdrawal amount")
if amount > self.balance:
self.log(f"Failed withdrawal of ${amount} - insufficient funds")
raise ValueError("Insufficient funds")
self.balance -= amount
self.log(f"Withdrew ${amount}. New balance: ${self.balance}")
# Test
account = SecureBankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
try:
account.withdraw(5000) # Will fail
except ValueError as e:
print(f"\nError: {e}")
account.show_logs()
Example 5: GUI Component System¶
class Component:
"""Base UI component."""
def __init__(self, name):
self.name = name
self.visible = True
def show(self):
self.visible = True
print(f"{self.name} is now visible")
def hide(self):
self.visible = False
print(f"{self.name} is now hidden")
class Clickable:
"""Add click functionality."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.on_click = None
def set_click_handler(self, handler):
"""Set click event handler."""
self.on_click = handler
def click(self):
"""Trigger click event."""
if self.on_click:
print(f"{self.name} clicked!")
self.on_click()
else:
print(f"{self.name} has no click handler")
class Draggable:
"""Add drag functionality."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.position = (0, 0)
def drag_to(self, x, y):
"""Drag to new position."""
self.position = (x, y)
print(f"{self.name} dragged to ({x}, {y})")
class Resizable:
"""Add resize functionality."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.width = 100
self.height = 100
def resize(self, width, height):
"""Resize component."""
self.width = width
self.height = height
print(f"{self.name} resized to {width}x{height}")
# Different component types
class Button(Component, Clickable):
"""Clickable button."""
pass
class Window(Component, Draggable, Resizable):
"""Draggable and resizable window."""
pass
class IconButton(Component, Clickable, Draggable):
"""Button that can be clicked and dragged."""
pass
# Test components
button = Button("Submit Button")
button.set_click_handler(lambda: print("Form submitted!"))
button.click()
window = Window("Main Window")
window.resize(800, 600)
window.drag_to(100, 100)
icon = IconButton("Settings Icon")
icon.set_click_handler(lambda: print("Settings opened!"))
icon.drag_to(50, 50)
icon.click()
print(f"\n--- Button MRO ---")
print([cls.__name__ for cls in Button.mro()])
print(f"\n--- IconButton MRO ---")
print([cls.__name__ for cls in IconButton.mro()])
Example 6: Role-Based Access System¶
class User:
"""Base user class."""
def __init__(self, username, email):
self.username = username
self.email = email
self.is_active = True
def deactivate(self):
self.is_active = False
class CanRead:
"""Read permission."""
def read(self, document):
print(f"{self.username} reading: {document}")
class CanWrite:
"""Write permission."""
def write(self, document, content):
print(f"{self.username} writing to: {document}")
class CanDelete:
"""Delete permission."""
def delete(self, document):
print(f"{self.username} deleting: {document}")
class CanManageUsers:
"""User management permission."""
def create_user(self, username):
print(f"{self.username} created user: {username}")
def delete_user(self, username):
print(f"{self.username} deleted user: {username}")
# Different user roles
class Viewer(User, CanRead):
"""Can only read."""
pass
class Editor(User, CanRead, CanWrite):
"""Can read and write."""
pass
class Admin(User, CanRead, CanWrite, CanDelete, CanManageUsers):
"""Full permissions."""
pass
# Test roles
viewer = Viewer("john", "john@email.com")
viewer.read("report.pdf")
# viewer.write("report.pdf", "text") # ERROR - no write permission
editor = Editor("jane", "jane@email.com")
editor.read("document.txt")
editor.write("document.txt", "Updated content")
# editor.delete("document.txt") # ERROR - no delete permission
admin = Admin("admin", "admin@email.com")
admin.read("config.yaml")
admin.write("config.yaml", "new settings")
admin.delete("old_file.txt")
admin.create_user("newuser")
print(f"\n--- Admin Capabilities ---")
print(f"Admin MRO: {[cls.__name__ for cls in Admin.mro()]}")
Example 7: Smart Device System¶
class Device:
"""Base device class."""
def __init__(self, name, brand):
self.name = name
self.brand = brand
self.is_on = False
def power_on(self):
self.is_on = True
print(f"{self.name} powered on")
def power_off(self):
self.is_on = False
print(f"{self.name} powered off")
class WiFiEnabled:
"""WiFi capability."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.connected_network = None
def connect_wifi(self, network):
self.connected_network = network
print(f"{self.name} connected to {network}")
def disconnect_wifi(self):
print(f"{self.name} disconnected from {self.connected_network}")
self.connected_network = None
class BluetoothEnabled:
"""Bluetooth capability."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.paired_devices = []
def pair_bluetooth(self, device):
self.paired_devices.append(device)
print(f"{self.name} paired with {device}")
def unpair_bluetooth(self, device):
self.paired_devices.remove(device)
print(f"{self.name} unpaired from {device}")
class VoiceControlled:
"""Voice control capability."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.wake_word = "Hey Device"
def voice_command(self, command):
if self.is_on:
print(f"{self.name} executing: {command}")
else:
print(f"{self.name} is off")
class Battery:
"""Battery capability."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.battery_level = 100
def check_battery(self):
print(f"{self.name} battery: {self.battery_level}%")
def charge(self):
self.battery_level = 100
print(f"{self.name} fully charged")
# Smart devices with different capabilities
class SmartPhone(Device, WiFiEnabled, BluetoothEnabled,
VoiceControlled, Battery):
"""Full-featured smartphone."""
pass
class SmartSpeaker(Device, WiFiEnabled, BluetoothEnabled, VoiceControlled):
"""Smart speaker (no battery, always plugged in)."""
pass
class SmartWatch(Device, BluetoothEnabled, Battery):
"""Smart watch (no WiFi, has battery)."""
pass
# Test devices
print("=== Smartphone ===")
phone = SmartPhone("iPhone", "Apple")
phone.power_on()
phone.connect_wifi("Home WiFi")
phone.pair_bluetooth("AirPods")
phone.voice_command("Call Mom")
phone.check_battery()
print("\n=== Smart Speaker ===")
speaker = SmartSpeaker("Echo", "Amazon")
speaker.power_on()
speaker.connect_wifi("Home WiFi")
speaker.voice_command("Play music")
# speaker.check_battery() # ERROR - no battery
print("\n=== Smart Watch ===")
watch = SmartWatch("Apple Watch", "Apple")
watch.power_on()
watch.pair_bluetooth("iPhone")
# watch.connect_wifi("WiFi") # ERROR - no WiFi
watch.check_battery()
print(f"\n=== Smartphone MRO ===")
for cls in SmartPhone.mro():
print(cls.__name__)
Practice Exercises¶
Beginner Exercises¶
Flying Animals
Create
Animal,Flyer,SwimmerclassesCreate
Duck,Penguin,Eaglewith appropriate inheritanceTest which animals can fly/swim
Media Player
Create
VideoPlayer,AudioPlayerbase classesCreate
MultimediaPlayerinheriting from bothImplement play methods
Shape Properties
Create
Colored,Texturedmixin classesCreate
ColoredCircle,TexturedSquareclassesDisplay properties
Student Roles
Create
Student,Athlete,ScholarclassesCreate
ScholarAthletecombining bothTrack activities
File Permissions
Create
Readable,Writable,ExecutablemixinsCreate different file types
Test permissions
Intermediate Exercises¶
Smart Home System
Create device base class
Add WiFi, Bluetooth, Voice mixins
Create different smart devices
Implement control systems
Game Character Skills
Create skill mixins (Magic, Archery, Sword)
Create character types combining skills
Implement combat system
Social Media Features
Create
Likeable,Commentable,ShareablemixinsCreate different post types
Track engagement
Vehicle Types
Create
LandVehicle,WaterVehicle,AirVehicleCreate amphibious and flying cars
Handle mode switching
Employee Benefits
Create
HealthInsurance,Retirement,StockOptionsmixinsCreate employee types with different benefits
Calculate total compensation
Advanced Exercises¶
Plugin System
Dynamic mixin application
Plugin loader
Conflict resolution
Dependency management
ORM System
Model base class
Serialization mixins
Validation mixins
Query capabilities
Game Engine Components
Entity component system
Renderable, Collidable, Scriptable mixins
Performance optimization
Authentication System
OAuth, SAML, JWT mixins
Multi-factor authentication
Session management
Content Delivery
Cacheable, Compressible, Encryptable mixins
Different content types
Delivery optimization
Common Mistakes to Avoid¶
Mistake 1: Conflicting Methods¶
❌ Wrong:
class A:
def method(self):
return "A"
class B:
def method(self):
return "B"
class C(A, B): # Which method wins?
pass
c = C()
print(c.method()) # "A" (first parent wins, but confusing!)
✅ Correct:
class C(A, B):
def method(self):
# Explicitly define behavior
return f"C uses: {super().method()}"
Mistake 2: Not Understanding MRO¶
❌ Wrong:
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
A.__init__(self) # Direct call
class C(A):
def __init__(self):
A.__init__(self) # Direct call
class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self) # A.__init__ called TWICE!
✅ Correct:
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
super().__init__() # Use super()
class C(A):
def __init__(self):
super().__init__() # Use super()
class D(B, C):
def __init__(self):
super().__init__() # Calls each parent once
Mistake 3: Mutable Mixin Attributes¶
❌ Wrong:
class ListMixin:
items = [] # Shared by ALL instances!
def add_item(self, item):
self.items.append(item)
✅ Correct:
class ListMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.items = [] # Each instance gets own list
Mistake 4: Overusing Multiple Inheritance¶
❌ Wrong:
# Too complex!
class SuperUser(User, Admin, Moderator,
PowerUser, PremiumUser, BetaTester):
pass
✅ Correct:
# Use composition or simplify
class User:
def __init__(self):
self.permissions = PermissionSet()
self.roles = RoleManager()
Real-World Applications¶
1. Django Models¶
Model mixins for timestamps
Serialization mixins
Permission mixins
Audit trail mixins
2. GUI Frameworks¶
Component mixins (Clickable, Draggable, Resizable)
Event handling
Style mixins
Layout mixins
3. Game Development¶
Component-based entities
Ability mixins
Status effect mixins
AI behavior mixins
4. Web Frameworks¶
View mixins
Authentication mixins
CRUD operation mixins
API serialization mixins
Challenge Projects¶
1. Plugin Architecture¶
Dynamic plugin loading
Feature mixins
Dependency resolution
Version compatibility
2. Game Character System¶
Ability tree system
Equipment slots
Status effects
Skill combinations
3. CMS with Roles¶
Content types
Permission mixins
Workflow states
Publishing pipeline
4. IoT Device Manager¶
Protocol mixins (WiFi, Bluetooth, Zigbee)
Sensor capabilities
Actuator controls
Power management
5. API Framework¶
Serialization formats
Authentication methods
Rate limiting
Caching strategies