Multiple Inheritance & C3 Linearization
Master Python multiple inheritance: the diamond problem, the recursive C3 Linearization merge algorithm, MRO monotonicity invariants, and cooperative super().
Python natively supports multiple inheritance: a class can inherit from two or more parent classes simultaneously.
However, multiple inheritance introduces serious theoretical challenges, most notably the Diamond Inheritance Problem, where multiple inheritance paths diverge and rejoin at a common base class.
To resolve method calls deterministically without duplicate executions or ambiguous dispatch, Python uses the C3 Linearization Algorithm to construct a single, ordered list known as the Method Resolution Order (MRO).
Adopted in Python 2.3 to guarantee monotonicity and local precedence across complex hierarchies.
1. The Diamond Inheritance Dilemma#
Consider a classic diamond hierarchy:
If class D calls a method defined in A and overridden in B and C, which version should execute? In what order should cooperative initializers run?
Legacy Python 2.2 used a naive Depth-First Search (DFS) order (D -> B -> A -> C -> A -> object), which violated fundamental object-oriented principles: it evaluated grandparent A before sibling parent C!
2. The Formal C3 Linearization Algorithm#
Python resolves inheritance graphs using C3 Linearization. The algorithm defines the linearization of class inheriting from parent classes through a recursive merge operation:
The Merge Selection Rule#
The merge operator evaluates the list candidates:
- Examine the head (first element) of the first list in the merge set.
- Check if this head appears in the tail (all elements after the first) of any other list in the merge set.
- If it does not appear in any tail:
- Append that head class to the output MRO list.
- Remove that class from every list in the merge set.
- If it appears in a tail:
- Skip to the head of the next list in the merge set and repeat the check.
- If all heads appear in tails, C3 aborts, raising
TypeError: Cannot create a consistent method resolution order (MRO).
Invariants Guaranteed by C3#
- Extended Child Precedence: Subclasses always precede parent classes ( appears before and ).
- Local Precedence Order: Parent order declared in class definition headers is preserved ( appears before ).
- Monotonicity: If class precedes class in , then strictly precedes in the MRO of any subclass of .
3. Step-by-Step Mathematical Derivation#
Let us calculate the MRO for the diamond hierarchy where , , and :
Now compute :
- Round 1: Head of list 1 is . Is in any tail?
- Tails:
(A, object),(A, object),(C). is not in any tail! - Pick . Output:
(D, B). - Remaining:
merge((A, object), (C, A, object), (C))
- Round 2: Head of list 1 is . Is in any tail?
- Tails:
(object),(A, object)is in the tail of list 2! Blocked! - Move to list 2 head: . Is in any tail?
- Tails:
(object),(A, object),()is not in any tail! - Pick . Output:
(D, B, C). - Remaining:
merge((A, object), (A, object))
- Round 3: Head is . is not in any tail. Pick . Output:
(D, B, C, A). - Round 4: Pick
object.
4. Demystifying super() in Cooperative Hierarchies#
A widespread misconception is that super() calls the immediate parent class. In Python, super() does not call the parent; it calls the NEXT class in the instance's MRO!
1234567891011121314151617181920212223242526class Base: def action(self): print("Base action") class Left(Base): def action(self): print("Left action start") super().action() print("Left action end") class Right(Base): def action(self): print("Right action start") super().action() print("Right action end") class Diamond(Left, Right): def action(self): print("Diamond action start") super().action() print("Diamond action end") d = Diamond() print(f"MRO Order: {[cls.__name__ for cls in Diamond.__mro__]}") print("\n=== Executing d.action() ===") d.action()
Notice that inside Left.action(), calling super().action() dispatched directly to Right.action(), even though Left does not inherit from Right! This cooperative dispatch allows every class in the diamond to execute exactly once.
5. Concept Verification#
Test your understanding of multiple inheritance and C3 linearization:
What does super().method() actually invoke in Python?
Which invariant is guaranteed by the C3 Linearization algorithm to ensure inheritance hierarchies remain predictable?