Inheritance & MRO
Python supports multiple inheritance and resolves methods with the Method Resolution Order (MRO). super() walks that order - essential for cooperative __init__ chains in mixins.
Recipe
class LogMixin:
def log(self, msg: str) -> None:
print(f"[{self.__class__.__name__}] {msg}")
class Service(LogMixin):
def run(self) -> None:
self.log("starting")When to reach for this:
- Sharing cross-cutting behavior via mixins (logging, serialization)
- Extending framework base classes (Django models, ABCs)
- Debugging unexpected method overrides in hierarchies
- Designing plugin APIs with template methods
Working Example
class Base:
def __init__(self) -> None:
self.base_init = True
class TrackingMixin:
def __init__(self, *args, **kwargs) -> None:
self.tracked = True
super().__init__(*args, **kwargs)
class TimestampMixin:
def __init__(self, *args, **kwargs) -> None:
self.timestamped = True
super().__init__(*args, **kwargs)
class Event(TrackingMixin, TimestampMixin, Base):
pass
if __name__ == "__main__":
e = Event()
print(e.tracked, e.timestamped, e.base_init)
print(Event.mro())What this demonstrates:
- Cooperative
super().__init__calls each class in MRO once - Mixins prepend behavior without deep trees
Event.mro()shows resolution order for debugging- All mixin inits run before
Basecompletes chain
Deep Dive
How It Works
- C3 linearization - MRO respects local precedence and monotonicity.
- super() proxy - Skips to next class in MRO, not necessarily direct parent.
- Method overriding - Subclass method shadows base;
super()calls next implementation. - Diamond problem - C3 ensures each class appears once in sensible order.
- init chaining - Every class in cooperative pattern must call
super().__init__.
Inspect MRO
HelpfulClass.mro() # list of classes in lookup order
HelpfulClass.__mro__ # tuple formPython Notes
# explicit override call
class Child(Parent):
def save(self) -> None:
super().save()
self.post_save_hook()Gotchas
- Missing super() in mixin - Breaks chain - some bases never initialize. Fix: Always
super().__init__(*args, **kwargs)in mixins. - Deep inheritance towers - Fragile overrides. Fix: Composition or protocol-based design.
- Mutable class attributes inherited - Shared list on subclass mutates parent view. Fix: Set per-instance in
__init__. - Mixing unrelated bases - Confusing MRO and API surface. Fix: Small mixins with one responsibility.
- super() without cooperative bases - Legacy classes not calling super break pattern. Fix: Adapt or avoid multiple inheritance.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Composition | Has-a relationships | True is-a polymorphism |
| Protocol typing | Structural interfaces | Need shared implementation |
| Functions + dispatch | Simple behavior reuse | Template method pattern fits |
| Single inheritance | Clear hierarchy | Only need one mixin layer |
FAQs
What is MRO?
Order Python searches bases for attributes/methods. Print with Class.mro().
Why super() not Parent.method()?
super() respects MRO for multiple inheritance - hardcoded parent skips cooperative mixins.
How many mixins?
Few small ones OK. Many mixins signal design smell - consider composition.
Can I inherit from two unrelated classes?
Technically yes - watch API clarity and MRO surprises. Often composition clearer.
abstract base in hierarchy?
ABC can sit in MRO enforcing methods before concrete classes.
override __init__ rules?
Call super().__init__ unless deliberately replacing whole chain - document why.
method resolution attribute?
Instance attr shadows class method - descriptors and data descriptors add nuance.
__slots__ inheritance?
Child slots must include parent slots empty declaration - complex; read docs before combining.
testing MRO?
Assert Class.mro()[1] == ExpectedParent when mixin order critical.
framework inheritance?
Django/FastAPI patterns often subclass framework classes - follow framework docs for super calls.
Related
- Abstract Base Classes - ABC in hierarchies
- Protocols & Structural Typing - alternative to inheritance
- Class vs Instance vs Static Methods - super with classmethods
- Object-Oriented Python Best Practices - composition guidance
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.