Inheritance & MRO

Difficulty: Intermediate

Question

Explain inheritance in Python, including MRO and the diamond problem.

Answer

Python supports single and multiple inheritance. When a class inherits from another, it gets all its attributes and methods.

MRO (Method Resolution Order) defines the order Python searches for methods in an inheritance hierarchy. Python uses C3 linearization to compute the MRO.

The Diamond Problem occurs when a class inherits from two classes that share a common ancestor. Python's MRO ensures each class in the hierarchy is called exactly once.

super() delegates method calls following the MRO, not just to the parent class. This is crucial for cooperative multiple inheritance.

Code examples

Basic Inheritance and super()

class Animal:
    def __init__(self, name, sound):
        self.name = name
        self.sound = sound
    
    def speak(self):
        return f"{self.name} says {self.sound}!"
    
    def __repr__(self):
        return f"Animal('{self.name}')"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, 'Woof')  # Call parent __init__
        self.breed = breed
    
    def fetch(self, item):
        return f"{self.name} fetches the {item}"

class ServiceDog(Dog):
    def __init__(self, name, breed, task):
        super().__init__(name, breed)
        self.task = task
    
    def work(self):
        return f"{self.name} performs: {self.task}"

dog = ServiceDog("Rex", "Labrador", "Guide")
print(dog.speak())   # From Animal
print(dog.fetch("ball"))  # From Dog
print(dog.work())    # From ServiceDog
print(isinstance(dog, Animal))  # True
print(issubclass(ServiceDog, Animal))  # True

super() calls the parent class method following the MRO. Each level of inheritance adds capabilities while reusing parent behavior.

Multiple Inheritance and Diamond Problem

class A:
    def greet(self):
        return "Hello from A"

class B(A):
    def greet(self):
        return "Hello from B"

class C(A):
    def greet(self):
        return "Hello from C"

class D(B, C):  # Diamond: D -> B -> C -> A
    pass

d = D()
print(d.greet())       # 'Hello from B' (B comes first in MRO)
print(D.__mro__)        # D -> B -> C -> A -> object
# or equivalently:
print(D.mro())

# Cooperative multiple inheritance with super()
class Base:
    def __init__(self, kwargs):
        print(f"Base.__init__")

class Left(Base):
    def __init__(self, left_val=0, kwargs):
        print(f"Left.__init__({left_val})")
        super().__init__(kwargs)

class Right(Base):
    def __init__(self, right_val=0, kwargs):
        print(f"Right.__init__({right_val})")
        super().__init__(kwargs)

class Child(Left, Right):
    def __init__(self):
        print("Child.__init__")
        super().__init__(left_val=1, right_val=2)

c = Child()

MRO uses C3 linearization: depth-first, left-to-right, each class appears once. super() follows MRO, so Left.super() calls Right, not Base.

Mixins: Composable Behavior

# Mixins add behavior without being standalone classes
class JsonMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__, indent=2)

class LogMixin:
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class ValidateMixin:
    def validate(self):
        for key, value in self.__dict__.items():
            if value is None:
                raise ValueError(f"{key} cannot be None")
        return True

# Compose behavior through mixins
class User(JsonMixin, LogMixin, ValidateMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Alice", "alice@example.com")
print(user.to_json())
user.log("Created successfully")
print(user.validate())

Mixins are small, focused classes that add specific capabilities. They follow the 'composition over inheritance' principle using multiple inheritance.

Key points

Concepts covered

Inheritance, Multiple Inheritance, MRO, super(), Diamond Problem, Mixins