Difficulty: Intermediate
Explain inheritance in Python, including MRO and the diamond problem.
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.
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.
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 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.
Inheritance, Multiple Inheritance, MRO, super(), Diamond Problem, Mixins