Python Method Resolution Order

Learn how Python's C3 linearization determines method resolution order, how super() works, and how to handle diamond inheritance correctly.

8 min read

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.

pythonpython
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.

texttext
D
B
C
A
object

The 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.

pythonpython
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.

texttext
Combined -> Left -> Right -> Base

This 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.

pythonpython
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.

texttext
Database init
Logger init
App init

The 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.

pythonpython
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.

texttext
Cannot create a consistent method resolution order (MRO) for bases X, Y

The 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

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.
RunePowered by Rune AI

Frequently Asked Questions

What is the C3 linearization algorithm?

C3 linearization is the algorithm Python uses to compute the Method Resolution Order for classes with multiple inheritance. It produces a linear ordering of base classes that preserves the local precedence order of each class, respects the order of base classes as declared, and ensures that each class appears after all its parents. Python adopted C3 in version 2.3 to replace the older depth-first algorithm that had problems with diamond inheritance.

How do I use super() correctly in multiple inheritance?

Call super() without arguments in cooperative methods. Every class in the hierarchy should call super() in the same method to ensure the entire chain executes. The MRO determines which class super() calls next, not the direct parent. For this to work correctly, all classes must accept **kwargs and forward them, since you cannot predict which class super() will resolve to.

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.