- Parent Calling Mechanisms: Explicit Calls vs. Cooperative super()
- Method Resolution Order (MRO) & C3 Linearization Algorithm
- The Diamond Problem
- Incompatible Inheritance: Every Failure Scenario Explained
- Rules for Designing Cooperative Multiple Inheritance
Parent Calling Mechanisms: Explicit Calls vs. Cooperative super()
In Python, a subclass overriding a parent method can invoke ancestor behavior through two fundamentally different mechanisms: hardcoded direct calls or dynamic cooperative calls via super().
| Feature | Explicit Calling (Base.method(self)) |
Cooperative Calling (super().method()) |
|---|---|---|
| Resolution Target | Static, hardcoded class reference | Dynamic: Next class in the instance’s runtime MRO |
| Coupling | Tightly coupled to a specific parent name | Loosely coupled; agnostic of sibling hierarchy |
| Multiple Inheritance | ❌ Causes duplicate executions in diamond shapes | ✅ Guaranteed single execution per ancestor class |
| Flexibility | Rigid; breaks when mixins are injected | Highly extensible across complex class graphs |
– Implementation comparison:
class Parent: def greet(self) -> None: print("Hello from Parent") class ChildExplicit(Parent): def greet(self) -> None: # 1. Hardcoded: bypasses MRO traversal Parent.greet(self) print("Hello from ChildExplicit") class ChildSuper(Parent): def greet(self) -> None: # 2. Cooperative: delegates along the instance MRO chain super().greet() print("Hello from ChildSuper") |
Method Resolution Order (MRO) & C3 Linearization Algorithm
– What is C3 Linearization?
C3 Linearization is the deterministic algorithm introduced in Python 2.3 to flatten complex directed acyclic inheritance graphs (DAGs) into a single, predictable linear lookup list known as the Method Resolution Order (MRO).
– The Core Invariants of C3:
– Local Precedence Order: The left-to-right definition order declared in class Sub(A, B) is strictly preserved (A is visited before B).
– Monotonicity (Subclasses before Parents): A base class is never checked before any of its derived child classes.
– Extended Precedence (Order Consistency): A child class cannot contradict or reverse the relative order of ancestor classes established by any of its parents. If class A appears before class B in any parent’s MRO chain, A must strictly precede B in the final MRO.
– Inspecting the MRO at Runtime:
class Base: pass class Left(Base): pass class Right(Base): pass class Combined(Left, Right): pass # Inspecting the linearized chain: print(Combined.mro()) # Output: [<class '__main__.Combined'>, <class '__main__.Left'>, <class '__main__.Right'>, <class '__main__.Base'>, <class 'object'>] |
The Diamond Problem
– The Diamond Structure in Practice:
A diamond hierarchy occurs when a specialized class (e.g., SecureArchiveWriter) inherits from two intermediate classes (EncryptedWriter and CompressedWriter), both deriving from a common base class (BaseWriter).
Note: We illustrate this mechanism using __init__(), but Python’s cooperative resolution works identically with any custom or built-in method (such as write(), save(), or close()).
class BaseWriter: def __init__(self) -> None: print("BaseWriter.__init__ (Allocating base buffer)") super().__init__() class EncryptedWriter(BaseWriter): def __init__(self) -> None: print("EncryptedWriter.__init__ (Setting up encryption keys)") super().__init__() class CompressedWriter(BaseWriter): def __init__(self) -> None: print("CompressedWriter.__init__ (Configuring compression algorithm)") super().__init__() class SecureArchiveWriter(EncryptedWriter, CompressedWriter): def __init__(self) -> None: print("SecureArchiveWriter.__init__ (Starting pipeline initialization)") super().__init__() # 1. Inspect the MRO blueprint BEFORE instantiation: print("MRO Resolution Chain:") print([cls.__name__ for cls in SecureArchiveWriter.mro()]) # 2. Instantiating the class - execution follows the exact MRO order: print("\nExecution Output:") writer = SecureArchiveWriter() |
MRO Resolution Chain: ['SecureArchiveWriter', 'EncryptedWriter', 'CompressedWriter', 'BaseWriter', 'object'] Execution Output: SecureArchiveWriter.__init__ (Starting pipeline initialization) EncryptedWriter.__init__ (Setting up encryption keys) CompressedWriter.__init__ (Configuring compression algorithm) BaseWriter.__init__ (Allocating base buffer) |
– Why this valid structure succeeds:
1. Monotonicity is respected: SecureArchiveWriter precedes its parents (EncryptedWriter, CompressedWriter), and both precede their shared ancestor (BaseWriter).
2. Local Precedence is preserved: EncryptedWriter is declared first in the base tuple, so it is visited before CompressedWriter.
3. Zero Order Contradictions: Neither parent imposes a conflicting relative order on BaseWriter. Each class in the diamond runs exactly once without duplicates.
– Why does EncryptedWriter call CompressedWriter instead of BaseWriter?
In Python, super() does not mean « call my lexical parent class ».
Instead, super() means « call the next class in the active instance’s runtime MRO ».
– When SecureArchiveWriter() is instantiated, the active instance is of type SecureArchiveWriter.
– Inside EncryptedWriter.__init__, super().__init__() inspects the active instance’s MRO chain: [SecureArchiveWriter, EncryptedWriter, CompressedWriter, BaseWriter, object].
– The class immediately following EncryptedWriter in that chain is CompressedWriter (its sibling in the diamond), not BaseWriter.
– Only after CompressedWriter.__init__ runs its own super().__init__() does execution reach BaseWriter, terminating at object.
Incompatible Inheritance: Every Failure Scenario Explained
Python validates class inheritance graphs at module load time. If an inheritance hierarchy creates conflicting ordering rules or violates C3 invariants, Python immediately halts execution and raises a TypeError before any instance can even be created. Below are the three core failure scenarios with self-contained class declarations.
Failure Case 1: Monotonicity Violation (Parent Listed Before Child)
The Invariant: A parent class can never appear before any of its derived subclasses in the inheritance list.
class BaseWriter: pass class EncryptedWriter(BaseWriter): pass # Invalid: BaseWriter (parent) is placed before EncryptedWriter (child) class BrokenWriter(BaseWriter, EncryptedWriter): pass |
TypeError: Cannot create a consistent method resolution order (MRO) for bases BaseWriter, EncryptedWriter |
Why this crashes:
– Local Precedence demands that BaseWriter must be inspected before EncryptedWriter because of the declaration order inside the parentheses: (BaseWriter, EncryptedWriter).
– Monotonicity demands that EncryptedWriter must be inspected before BaseWriter because it inherits from it.
– These two constraints directly contradict each other. Python cannot satisfy both, so class creation fails immediately.
Failure Case 2: Extended Precedence Violation (Contradictory Ancestor Order)
The Invariant: A child class cannot reverse or contradict the relative order of ancestor classes established by any of its parents.
class BaseWriter: pass class EncryptedWriter(BaseWriter): pass class CompressedWriter(BaseWriter): pass # PipelineAlpha establishes that EncryptedWriter must precede CompressedWriter class PipelineAlpha(EncryptedWriter, CompressedWriter): pass # PipelineBeta establishes the opposite: CompressedWriter must precede EncryptedWriter class PipelineBeta(CompressedWriter, EncryptedWriter): pass # Invalid: combining both creates an unsolvable order deadlock class MasterPipeline(PipelineAlpha, PipelineBeta): pass |
TypeError: Cannot create a consistent method resolution order (MRO) for bases PipelineAlpha, PipelineBeta |
Why this crashes:
– Inheriting from PipelineAlpha requires the MRO order: EncryptedWriter → CompressedWriter.
– Inheriting from PipelineBeta requires the reverse MRO order: CompressedWriter → EncryptedWriter.
– C3 Linearization cannot construct a single linear list where two classes are simultaneously before and after each other.
Failure Case 3: Grandparent / Subclass Ordering Conflict
The Invariant: Listing intermediate ancestors alongside a derived class out of order breaks C3’s topological sorting.
class BaseWriter: pass class EncryptedWriter(BaseWriter): pass class CompressedWriter(BaseWriter): pass class SecureArchiveWriter(EncryptedWriter, CompressedWriter): pass # Invalid: SecureArchiveWriter is listed AFTER its own parent classes class InvalidExporter(EncryptedWriter, CompressedWriter, SecureArchiveWriter): pass |
TypeError: Cannot create a consistent method resolution order (MRO) for bases EncryptedWriter, CompressedWriter, SecureArchiveWriter |
Why this crashes:
– SecureArchiveWriter is a specialized subclass of both EncryptedWriter and CompressedWriter.
– Listing the parents first puts them ahead of their own derived child class in the search order, which breaks monotonicity.
Rules for Designing Cooperative Multiple Inheritance
– 1. Universal use of super():
Never mix Parent.method(self) and super().method() within the same class hierarchy. A single hardcoded call breaks the dynamic MRO traversal for all derived classes down the chain.
– 2. Forwarding arbitrary arguments with **kwargs:
Because a class does not know which sibling will follow it in a future derived class’s MRO, constructors must accept and forward unconsumed keyword arguments:
class CooperativeBase: def __init__(self, **kwargs) -> None: # object.__init__ accepts no parameters, terminating the chain super().__init__() class Alpha(CooperativeBase): def __init__(self, alpha_val: int, **kwargs) -> None: self.alpha_val = alpha_val super().__init__(**kwargs) class Beta(CooperativeBase): def __init__(self, beta_val: str, **kwargs) -> None: self.beta_val = beta_val super().__init__(**kwargs) class CombinedClass(Alpha, Beta): pass # Clean cooperative parameter dispatch: instance = CombinedClass(alpha_val=42, beta_val="test") print(instance.alpha_val, instance.beta_val) # 42 test |