Cooperative Multiple Inheritance — Core Concepts
The Central Idea
Python allows a class to inherit from multiple parents. When those parents define the same method (say, __init__), Python needs a way to call all of them in a predictable order. The solution is cooperative multiple inheritance: every class in the chain calls super(), which forwards the call to the next class in the Method Resolution Order (MRO).
How super() Actually Works
super() doesn’t just mean “call my parent.” It means “call the next class in the MRO.” This is a crucial distinction.
Consider classes A, B, and C, where C inherits from both A and B. When C calls super().__init__(), it calls A.__init__(). When A calls super().__init__(), it calls B.__init__() — even though B is not a parent of A. The MRO determines the full chain.
The MRO Chain
Python computes the MRO using the C3 linearization algorithm. You can inspect it with ClassName.__mro__ or ClassName.mro().
For class C(A, B), the MRO is typically: C → A → B → object.
This means:
C.__init__runs first.super()inCcallsA.__init__.super()inAcallsB.__init__.super()inBcallsobject.__init__.
Every class gets its turn.
The Cooperation Contract
For this to work, every class must follow two rules:
- Always call
super()in the method you’re overriding. - Accept
**kwargsand pass unrecognized arguments through — because different classes in the chain may expect different parameters.
Breaking either rule breaks the chain. If A doesn’t call super().__init__(), then B.__init__ never runs, and B’s attributes are never set.
Where This Matters
Mixin Classes
Mixins are the most common use case. A mixin adds one specific behavior (logging, serialization, caching) and cooperates with whatever other classes are in the chain:
class LoggingMixin— adds logging to any classclass TimestampMixin— adds created/updated timestampsclass SerializableMixin— adds to_dict() capability
Framework Base Classes
Django uses cooperative inheritance extensively. View, TemplateView, FormView, and mixins like LoginRequiredMixin all cooperate through super() calls.
Common Misconception
“super() calls the parent class.” Not exactly. super() calls the next class in the MRO, which might not be a direct parent. This is what makes cooperative inheritance work across complex hierarchies, but it’s also what trips people up when they assume direct parent-child relationships.
The **kwargs Pattern
When classes in the chain expect different constructor arguments, use **kwargs forwarding:
class A:
def __init__(self, x, **kwargs):
super().__init__(**kwargs)
self.x = x
class B:
def __init__(self, y, **kwargs):
super().__init__(**kwargs)
self.y = y
class C(A, B):
def __init__(self, x, y, z, **kwargs):
super().__init__(x=x, y=y, **kwargs)
self.z = z
Each class picks off the arguments it knows about and forwards the rest.
When It Goes Wrong
| Symptom | Cause |
|---|---|
TypeError: __init__() got unexpected keyword argument | A class in the chain doesn’t accept **kwargs |
| Attribute missing at runtime | A class forgot to call super().__init__() |
| Method called in wrong order | MRO not what you expected — check ClassName.__mro__ |
TypeError: Cannot create a consistent MRO | Contradictory inheritance hierarchy — C3 linearization fails |
Composition vs. Cooperative Inheritance
Not every “has multiple abilities” situation needs multiple inheritance. If the behaviors don’t need to share the same method chain, composition (holding references to helper objects) is simpler and less fragile. Use cooperative inheritance when behaviors genuinely need to layer on top of each other in a method chain.
One thing to remember: Cooperative multiple inheritance works through a contract — every class calls super() and passes along arguments it doesn’t recognize. Break the contract in one place, and the whole chain breaks.
See Also
- Python Abc Abstract Base Classes Why Python's ABC module is like a building inspector who checks your blueprints before construction begins
- Python Class Decorators Understand Class Decorators through an everyday analogy so Python behavior feels intuitive, not random.
- Python Composition Vs Inheritance Understand Composition Vs Inheritance through an everyday analogy so Python behavior feels intuitive, not random.
- Python Dataclasses Advanced Understand Dataclasses Advanced through an everyday analogy so Python behavior feels intuitive, not random.
- Python Descriptors Understand Descriptors through an everyday analogy so Python behavior feels intuitive, not random.