Multiple Inheritance & the MRO
Understand the C3 linearisation that decides method lookup order — and how super() cooperates.
Python allows a class to inherit from several parents. When you call a method, Python must decide which parent's version to use. It follows the Method Resolution Order (MRO): a single, deterministic ordering of every ancestor, computed by the C3 linearisation algorithm.
Reading the MRO
Every class exposes its MRO. Python searches it left to right, top to bottom, and — crucially — never visits a class before all of its subclasses:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print([c.__name__ for c in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']super() follows the MRO, not the parent
The big surprise: super() does not mean "my parent class." It means "the next class in the MRO." In a diamond like the one above, super() inside B can land on C, not A. This is what makes cooperative multiple inheritance work — every class calls super() and the whole chain runs exactly once.
Example
class Base:
def __init__(self, **kw):
print('Base.__init__')
# stops the chain cleanly at object
class LoggerMixin(Base):
def __init__(self, **kw):
print('LoggerMixin.__init__')
super().__init__(**kw) # next in MRO, not necessarily Base
class TimerMixin(Base):
def __init__(self, **kw):
print('TimerMixin.__init__')
super().__init__(**kw)
class Service(LoggerMixin, TimerMixin):
def __init__(self, name, **kw):
print(f'Service.__init__({name})')
super().__init__(**kw)
print('MRO:', ' -> '.join(c.__name__ for c in Service.__mro__))
print('--- constructing ---')
Service('api')
# Notice: TimerMixin runs because super() in LoggerMixin follows the MRO,
# and every __init__ fires exactly once.
# Output:
# MRO: Service -> LoggerMixin -> TimerMixin -> Base -> object
# --- constructing ---
# Service.__init__(api)
# LoggerMixin.__init__
# TimerMixin.__init__
# Base.__init__When to use it
- A mixin architecture uses super() correctly so that all mixins in the MRO chain are called in order.
- A developer inspects ClassName.__mro__ to debug which class provides a method in a diamond hierarchy.
- A framework uses the MRO to determine the correct order to apply multiple authentication mixins.
More examples
Inspect the MRO
Prints the C3-linearised MRO for a diamond hierarchy, showing the lookup order.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print([cls.__name__ for cls in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']super() follows the MRO
Each super().hello() call follows the MRO, so all classes in the chain contribute their prefix.
class A:
def hello(self): return 'A'
class B(A):
def hello(self): return 'B->' + super().hello()
class C(A):
def hello(self): return 'C->' + super().hello()
class D(B, C):
def hello(self): return 'D->' + super().hello()
print(D().hello()) # D->B->C->AMixin pattern with MRO
The LogMixin.save() logs and delegates to the next class in the MRO (Model) via super().
class LogMixin:
def save(self):
print('Logging save')
super().save()
class Model:
def save(self): print('Saving to DB')
class LoggedModel(LogMixin, Model): pass
LoggedModel().save()
# Logging save
# Saving to DB
Discussion