The Method Resolution Order, or MRO, is the linear sequence Python follows when searching for an attribute or method on a class. When you call obj.method(), Python walks the MRO of the object's type, checking each class in order until it finds a matching name.
The article on attribute lookup in Python explained the full lookup chain. The MRO is the specific ordering used during the class-level search in that chain. Understanding how Python computes this ordering is essential for working with multiple inheritance and the super built-in.
Single inheritance is straightforward: the MRO is the class, its parent, the grandparent, and so on up to object.
Multiple inheritance requires a deliberate algorithm to produce a consistent ordering that respects all the constraints imposed by the inheritance graph.
How Python computes the MRO
Python uses the C3 linearization algorithm, introduced in Python 2.3, to compute the MRO for classes with multiple inheritance. The algorithm produces an ordering that satisfies three constraints.
First, children appear before their parents. The class itself is always first in its own MRO.
Second, the order of parents declared in the class statement is preserved. If you write class D(B, C), then B appears before C in the MRO. Third, every class appears after all its own parents.
You can inspect the MRO of any class through the mro method or the mro attribute.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
for cls in D.__mro__:
print(cls.__name__)Class D inherits from B and C, both of which inherit from A. The MRO places D first, then B before C as declared, and A before object.
D
B
C
A
objectThe algorithm prevents inconsistent orderings. If a class hierarchy cannot be linearized while respecting all constraints, Python raises a TypeError at class definition time rather than producing an ambiguous MRO.
How the super built-in navigates the MRO
The super built-in does not always call the direct parent. It follows the MRO of the original object's type, starting from the class where super is called.
This is different from languages where super always means the parent class. In Python, calling super in class B might resolve to class C if the original object is an instance of a class that inherits from both.
class Base:
def describe(self):
return "Base"
class Left(Base):
def describe(self):
return "Left -> " + super().describe()
class Right(Base):
def describe(self):
return "Right -> " + super().describe()
class Combined(Left, Right):
def describe(self):
return "Combined -> " + super().describe()
obj = Combined()
print(obj.describe())The Combined class inherits from Left then Right. Calling super in Left's describe method resolves to Right, not Base, because the MRO of Combined is Combined, Left, Right, Base, object.
Combined -> Left -> Right -> BaseThis cooperative super pattern requires every class in the hierarchy to call super. If one class omits the call, the chain breaks and classes later in the MRO are never reached.
Diamond inheritance and cooperative methods
The diamond pattern occurs when two classes inherit from the same base and a third inherits from both. Without cooperative super, the base class method would be called twice or not at all.
class Logger:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(f"Logger init")
class Database:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(f"Database init")
class App(Logger, Database):
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(f"App init")
app = App()Each init calls super, which follows the MRO to the next class. The output shows each class initializing exactly once in MRO order.
Database init
Logger init
App initThe keyword argument forwarding with **kwargs is essential. You cannot predict which class super will resolve to, so every method must accept and forward arguments it does not recognize.
Resolving MRO conflicts
Not every inheritance graph can be linearized. Python detects inconsistent MROs at class definition time and raises an error rather than guessing.
class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass
try:
class C(A, B): pass
except TypeError as e:
print(e)Class A wants X before Y. Class B wants Y before X. Class C inherits from both and there is no consistent ordering that satisfies both constraints.
Cannot create a consistent method resolution order (MRO) for bases X, YThe fix is to restructure the class hierarchy to eliminate the conflicting constraints. Sometimes this means introducing intermediate base classes or using composition instead of inheritance.
Practical MRO patterns
For most single-inheritance code, the MRO is invisible and works automatically. You only need to think about it when designing mixins or handling diamond inheritance.
Mixins should be placed before the primary base class in the parent list. This ensures the mixin's methods override the base class methods. Mixins should call super to participate in cooperative inheritance.
Framework base classes that require cooperative initialization should document the super requirement and accept **kwargs to forward unknown arguments to the next class in the chain.
The article on class creation in Python covers how the MRO is finalized during class creation and how metaclasses can inspect or modify it.
Rune AI
Key Insights
- The MRO is a linear ordering of classes computed by the C3 linearization algorithm.
- Accessible via mro or the mro() method on any class.
- super() navigates the MRO, not the direct parent class.
- Diamond inheritance works correctly when all classes call super() cooperatively.
- Use **kwargs forwarding in cooperative methods to handle unknown arguments safely.
Frequently Asked Questions
What is the C3 linearization algorithm?
How do I use super() correctly in multiple inheritance?
Conclusion
Python's method resolution order is the backbone of its inheritance system. The C3 linearization ensures consistent, predictable method dispatch even in complex diamond hierarchies. Understanding MRO and super() lets you design class hierarchies that compose correctly and avoid the surprises that come from guessing which parent a method call will hit.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.