🔀 Multiple Inheritance

📺 Video Tutorial

Learn Python multiple inheritance in 8 minutes! 🐟 (8:12)

Watch on YouTube

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 scenarios

  • Decide 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:

  1. The class itself

  2. First parent (left to right)

  3. Second parent (left to right)

  4. … and so on

  5. The object class (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

  1. Flying Animals

    • Create Animal, Flyer, Swimmer classes

    • Create Duck, Penguin, Eagle with appropriate inheritance

    • Test which animals can fly/swim

  2. Media Player

    • Create VideoPlayer, AudioPlayer base classes

    • Create MultimediaPlayer inheriting from both

    • Implement play methods

  3. Shape Properties

    • Create Colored, Textured mixin classes

    • Create ColoredCircle, TexturedSquare classes

    • Display properties

  4. Student Roles

    • Create Student, Athlete, Scholar classes

    • Create ScholarAthlete combining both

    • Track activities

  5. File Permissions

    • Create Readable, Writable, Executable mixins

    • Create different file types

    • Test permissions

Intermediate Exercises

  1. Smart Home System

    • Create device base class

    • Add WiFi, Bluetooth, Voice mixins

    • Create different smart devices

    • Implement control systems

  2. Game Character Skills

    • Create skill mixins (Magic, Archery, Sword)

    • Create character types combining skills

    • Implement combat system

  3. Social Media Features

    • Create Likeable, Commentable, Shareable mixins

    • Create different post types

    • Track engagement

  4. Vehicle Types

    • Create LandVehicle, WaterVehicle, AirVehicle

    • Create amphibious and flying cars

    • Handle mode switching

  5. Employee Benefits

    • Create HealthInsurance, Retirement, StockOptions mixins

    • Create employee types with different benefits

    • Calculate total compensation

Advanced Exercises

  1. Plugin System

    • Dynamic mixin application

    • Plugin loader

    • Conflict resolution

    • Dependency management

  2. ORM System

    • Model base class

    • Serialization mixins

    • Validation mixins

    • Query capabilities

  3. Game Engine Components

    • Entity component system

    • Renderable, Collidable, Scriptable mixins

    • Performance optimization

  4. Authentication System

    • OAuth, SAML, JWT mixins

    • Multi-factor authentication

    • Session management

  5. 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