MRO: Method Resolution Order (C3 linearization)
Overview
Method Resolution Order (`MRO`) is the sequence in which Python looks for a method or attribute in a hierarchy of classes. Think of `MRO` like a search path for documents: if a document isn't in your desk, you check the department filing cabinet, then the company archive, strictly following a designated path so everyone finds the same document. You can inspect the resolution sequence of any class using two approaches: - `cls.__mro__`: A special attribute that returns a `tuple` of the classes in resolution order. Raises `AttributeError` if accessed on an instance rather than a class. - `cls.mro()`: A class method that computes and returns a `list` of the classes in resolution order. Raises `AttributeError` if called on an instance. Python uses the C3 linearization algorithm to guarantee a consistent lookup order. This algorithm enforces three strict rules for building the `MRO`: - Children precede parents: A subclass is always checked before its base classes. - Left-to-right processing: Multiple parent classes are checked in the exact order they are listed in the class definition. - Monotonicity: A class is never visited until all of its subclasses have been visited. class BaseUser: pass class Admin(BaseUser): pass class SuperUser(BaseUser, Admin): pass # TypeError: Cannot create a consistent method resolution This is the inconsistent `MRO` trap. Because `Admin` inherits from `BaseUser`, `Admin` must be checked first, but `class SuperUser(BaseUser, Admin)` explicitly requests checking `BaseUser` before `Admin`. Using `MRO` to properly manage `super()` calls is covered fully in `method-overriding`.
It prevents ambiguity in multiple inheritance (the `diamond problem`) by defining a clear, predictable path for attribute lookup.
Where used: Django, Pydantic, FastAPI
Why learn this
- Understanding how Python resolves conflicts in multiple inheritance
- Debugging complex class hierarchies in frameworks like Django
Code walkthrough
class A:
def ping(self):
print('A')
class B(A):
def ping(self):
print('B')
class C(A):
def ping(self):
print('C')
class D(B, C):
pass
d = D()
d.ping()
Focus: class D(B, C): # B is listed before C, so D's MRO checks B before C
Aha moment
class Top:
name = 'Top'
class Left(Top):
pass
class Right(Top):
name = 'Right'
class Bottom(Left, Right):
pass
print(Bottom.name)
Prediction: What does `Bottom.name` print?
Common guess: `Top`, because `Left` is the first parent and it inherits from `Top`.
Python's `MRO` checks `Right` before `Top`. Even though `Left` is the first parent, `Top` is a shared base class, so Python delays checking `Top` until all subclasses (like `Right`) are checked.
Common mistakes
- Assuming depth-first search: Beginners often think Python searches the entire depth of the first parent class before checking the second parent. In reality, Python's C3 linearization merges the class hierarchy intelligently to keep shared base classes at the end.
- Inconsistent MRO errors: Creating a class hierarchy where it's mathematically impossible to satisfy the C3 rules (like inheriting from `A` and `B`, but `B` inherits from `A`) raises a `TypeError`.
Glossary
- linearization algorithm
- A mathematical set of rules Python uses to flatten a complex family tree of classes into a single, straightforward list, e.g. `D.__mro__` shows the resolved order for class `D`.
- diamond problem
- An ambiguity that arises when a class inherits from two different classes that both share the same base class, e.g. `class D(B, C)` where both `B` and `C` inherit from `A`.
Recall questions
- What algorithm does Python use to determine the Method Resolution Order (`MRO`)?
- How does MRO resolve ambiguity in multiple inheritance?
- What is the difference between `cls.__mro__` and `cls.mro()`?
Understanding checks
What is the output of this code?
B
`class D` inherits from `B` and `C` (in that order). Following the Method Resolution Order (`MRO`), Python checks `D`, then `B`. Since `name` is found in `B`, it prints `B` without ever checking `C` or `A`.
Why does Python's C3 linearization algorithm prevent ambiguity in the `diamond problem`?
It guarantees a consistent lookup order where subclasses are always checked before their base classes, and multiple parent classes are checked in the left-to-right order they are specified.
This deterministic set of rules ensures that developers always know exactly which inherited method or attribute will be executed, removing any ambiguity from complex multiple inheritance structures.
What happens when this code is executed?
It raises an AttributeError.
The `__mro__` attribute is only available on the class itself (`App`), not on instances of the class (`app`). Attempting to access it on an instance raises an `AttributeError`.
Practice tasks
Check the MRO
Modify the starter code to print the Method Resolution Order (MRO) of the `Combined` class using either the `__mro__` attribute or `.mro()` method.
Challenge
Trigger an MRO Error
Create a class hierarchy that causes a `TypeError: Cannot create a consistent method resolution` when defined. You need at least three classes to create an inconsistent inheritance graph.
Continue learning
Previous: Class Creation and Instantiation
Next: Method Overriding