Python MRO Method: How Method Resolution Works
python mro method: Learn how Python's MRO determines method lookup order in multiple inheritance, how C3 linearization works, and how to inspect and debug it.
When a class inherits from multiple parents, Python must decide which method to call when the same name appears in several classes. The order in which Python searches the inheritance graph is the method resolution order (MRO). This is the python mro method that determines whether super() reaches the intended class and which implementation wins in a conflict. Understanding the MRO is essential for designing class hierarchies that behave predictably.
How Python Resolves Method Lookup
Every class in Python has an attribute __mro__ that is a tuple of classes in the order they are searched when looking up a method or attribute. For a single-inheritance chain, the order is straightforward: the class itself, then its parent, then the parent's parent, and so on up to object. With multiple inheritance, the order is computed by an algorithm called C3 linearization. This algorithm produces a linear order that respects two constraints: local precedence order (parents are searched in the order they appear in the class definition) and monotonicity (if a class appears before another in one MRO, it must appear before it in all MROs that include both).
The C3 Linearization Algorithm
C3 linearization merges the MROs of the parent classes while preserving the local precedence order. The algorithm works by repeatedly selecting the first class from the list of candidate MROs that does not appear in the tail of any other list. This process continues until all classes are placed. The result is a consistent order that avoids ambiguous lookups. Python implements this algorithm in the type system, and it is the reason why a diamond-shaped hierarchy resolves to a specific order rather than causing an error.
Consider a classic diamond:
class A: def greet(self): return "A" class B(A): def greet(self): return "B" class C(A): def greet(self): return "C" class D(B, C): pass
The MRO for D is D -> B -> C -> A -> object. This order ensures that B is preferred over C, and C is preferred over A. When you call D().greet(), you get "B". The super() calls inside these methods follow the same MRO, so a super() call in B will go to C, not to A. This behavior is often surprising but is a direct consequence of the linearization.
Inspecting the MRO of a Class
You can inspect the MRO directly using the __mro__ attribute or the mro() method. The mro() method returns a list, while __mro__ is a tuple. Both show the same order. For example:
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) print(D.mro()) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
The help(D) output also displays the MRO at the top. When debugging a method resolution issue, printing __mro__ is the fastest way to see the exact lookup order.
Common Misconceptions About MRO and super()
A frequent misconception is that super() always calls the parent class directly. In a multiple-inheritance scenario, super() follows the MRO of the instance's class, not the class where the method is defined. This means the next class in the MRO could be a sibling, not a parent. Another misconception is that the order of base classes in the class definition is the same as the MRO. While local precedence order is respected, the full MRO also accounts for the MROs of all ancestors, so the final order may not match a simple left-to-right reading.
Consider:
class X: def method(self): return "X" class Y(X): def method(self): return "Y" + super().method() class Z(X): def method(self): return "Z" + super().method() class W(Y, Z): pass
The MRO for W is W -> Y -> Z -> X -> object. Calling W().method() returns "YZX". The super() in Y calls Z.method, and the super() in Z calls X.method. This cooperative behavior is why the MRO must be consistent across the hierarchy.
Practical Implications for Class Design
Deep and wide inheritance hierarchies can become difficult to maintain because the MRO is not always intuitive. When you override a method, you need to know where it sits in the MRO to predict which implementation will be called. Using super() in a cooperative way requires that all classes in the hierarchy follow the same signature and call super() appropriately. If one class breaks the chain, method resolution can skip classes or raise errors.
Performance is rarely a concern because method lookup is cached in the type's __mro__ and attribute access is optimized. The bigger cost is cognitive overhead: every new base class changes the MRO of all subclasses. For this reason, prefer composition over inheritance when the relationship is not a true "is-a" hierarchy. If you must use multiple inheritance, keep the hierarchy shallow and document the expected MRO.
Debugging MRO Errors
Python raises a TypeError when a consistent MRO cannot be computed, typically when the base classes have incompatible orderings. For example, if you try to create a class that inherits from two classes whose MROs conflict, you get an error like:
class A: pass class B(A): pass class C(A): pass class D(B, C): pass # This works # But this fails: class E(C, B): pass # TypeError: Cannot create a consistent method resolution order (MRO) for bases B, C
The error message is explicit. When you see it, review the base class order and the MRO of each base. Changing the order of bases often resolves the conflict. In more complex cases, you may need to refactor the hierarchy to remove the inconsistency.
Using MRO in Runtime Decisions
Occasionally you need to know the MRO at runtime, for example to implement a mixin that behaves differently depending on which classes are present. You can iterate over type(self).__mro__ to check for the presence of a marker class or to call a method from a specific class in the order. This pattern is common in frameworks that use mixins to add optional behavior. However, relying on MRO introspection makes your code more coupled to the class structure, so use it sparingly.
Testing Method Resolution Behavior
When you change a class hierarchy, tests can catch unexpected method resolution changes. Write tests that assert the return value of a method that is overridden in multiple bases. For example, if you expect W().method() to return "YZX", encode that in a test. If someone reorders bases later, the test will fail, alerting you to the change. This is a practical way to keep MRO behavior stable.
When to Avoid Multiple Inheritance
Multiple inheritance is powerful but can lead to fragile designs. If you find yourself constantly inspecting __mro__ to understand why a method is called, consider whether a simpler design would work. Composition, where a class holds an instance of another class, often provides clearer behavior and easier testing. The MRO is a deterministic algorithm, but the mental model required to predict it grows with the number of classes and their relationships. For most applications, a single-inheritance tree with mixins that do not override the same methods is sufficient.