Python MRO: Method Resolution Order Explained
OOP Internals
Method Resolution Order
Python uses the C3 linearization algorithm to pick the next class in an inheritance chain. Master it to debug complex hierarchies.
What the MRO is
When you access obj.method, Python needs one unambiguous answer to "which class supplies it?". With single inheritance the answer is easy — walk up the chain. With multiple inheritance, several paths can lead to the same ancestor, so Python flattens the whole hierarchy into a single ordered list per class: the method resolution order. Every attribute lookup, and every super() call, just walks that list front to back.
Introspect
class A: ...
class B(A): ...
class C(A): ...
class D(B, C): ...
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
Use inspect.getmro() or the __mro__ attribute to debug lookups. D.mro() returns the same thing as a list — handy for cleaner printing:
print([cls.__name__ for cls in D.mro()])
# ['D', 'B', 'C', 'A', 'object']
Rules of C3 linearization
The algorithm has an academic name, but the rules it enforces are intuitive:
- A class appears before its parents —
DbeforeBandC, both beforeA. - Order respects the order listed in the class definition —
BbeforeCbecauseD(B, C). - Each class appears exactly once, and parents keep their relative order everywhere.
That's why the order above is D, B, C, A and not D, B, A, C: A is also C's parent, so rule 1 pushes it after every class that inherits from it. The shared ancestor sinks to the end — which is exactly what makes the diamond problem solvable.
super() follows the MRO, not the parent
super() does not mean "my parent class". It means "the next class after me in the MRO of the instance being used". Watch it route through a sibling:
class Base:
def greet(self):
print("Base.greet")
class Left(Base):
def greet(self):
print("Left.greet")
super().greet()
class Right(Base):
def greet(self):
print("Right.greet")
super().greet()
class Child(Left, Right):
def greet(self):
print("Child.greet")
super().greet()
Child().greet()
# Child.greet
# Left.greet
# Right.greet <- Left's super() called Right, not Base
# Base.greet
Left knows nothing about Right, yet its super().greet() lands there — because for a Child instance the MRO is Child, Left, Right, Base, and Right is what comes after Left. The same code run on a plain Left instance would jump straight to Base:
Left().greet()
# Left.greet
# Base.greet
This is what makes cooperative mixin chains work: each class forwards to "whoever is next", and the MRO decides who that is.
When no consistent order exists
Sometimes the rules contradict each other, and Python refuses to create the class at all:
class A: ...
class B(A): ...
class C(A, B): ...
# TypeError: Cannot create a consistent method resolution order (MRO)
# for bases A, B
C(A, B) demands that A come before B (rule 2), but B subclasses A, so B must come before A (rule 1). No ordering satisfies both. The fix is almost always to swap the bases — class C(B, A) works, and since B already inherits from A, plain class C(B) says the same thing. In real codebases this error usually appears when different classes list the same set of mixins in different orders; pick one canonical order and use it everywhere.
Frequently Asked Questions
How do I see a class's MRO?
Read the __mro__ attribute (a tuple), call the mro() classmethod (a list), or use inspect.getmro(). All three give the same order, starting with the class itself and ending with object.
print(dict.__mro__)
# (<class 'dict'>, <class 'object'>)
print(bool.mro())
# [<class 'bool'>, <class 'int'>, <class 'object'>]
Does super() call the parent class?
No — it calls the next class in the MRO of the instance, which can be a sibling the current class has never heard of. In Child(Left, Right), a super() call inside Left resolves to Right, not to Left's own base. Thinking of super() as 'next in line' rather than 'my parent' is the key mental model for multiple inheritance.
When do I actually hit MRO errors in practice?
Almost always from inconsistent base ordering: one class declares (MixinA, MixinB) and another declares (MixinB, MixinA), then something inherits from both — C3 cannot honor both orders and raises TypeError at class definition time. Listing a class alongside its own subclass, as in C(A, B) where B inherits from A, triggers the same error. Standardize your mixin order across the codebase and the problem disappears.