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

Example · python
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.

Example · python
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.

Example · python
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->A

Mixin pattern with MRO

The LogMixin.save() logs and delegates to the next class in the MRO (Model) via super().

Example · python
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

  • Be the first to comment on this lesson.