What does "mro()" do?
Categories:
Understanding Python's Method Resolution Order (MRO) with mro()

Explore the mro()
method in Python, its role in inheritance, and how it determines the order in which methods are searched in complex class hierarchies.
In Python, when you call a method on an object, and that method is not directly defined in the object's class, Python needs a systematic way to find where that method is defined in the class hierarchy. This search order is known as the Method Resolution Order (MRO). The mro()
method, available on all class objects, provides a clear, explicit way to inspect this order, which is crucial for understanding inheritance and avoiding unexpected behavior in complex class structures.
What is Method Resolution Order (MRO)?
MRO defines the sequence in which Python searches for a method or attribute in a class hierarchy. When a class inherits from multiple parent classes (multiple inheritance), the MRO becomes particularly important. Python 2 and Python 3 handle MRO differently. Python 3 uses the C3 linearization algorithm, which guarantees a consistent and predictable order, ensuring that a class always appears before its parents and that the order of direct base classes is preserved.
class A:
def method(self):
print("Method from A")
class B(A):
pass
class C(A):
def method(self):
print("Method from C")
class D(B, C):
pass
print(D.mro())
# Expected output: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
Basic example demonstrating mro()
in a simple inheritance chain.
The C3 Linearization Algorithm
The C3 linearization algorithm is the standard MRO algorithm used in Python 3. It ensures two main properties:
- Monotonicity: If a class C precedes a class P in the MRO of C, then C must also precede P in the MRO of any subclass of C.
- Local Precedence Order: The order of direct base classes is preserved. If a class inherits from
(Base1, Base2, ...)
, thenBase1
will always appear beforeBase2
in the MRO, provided it doesn't violate monotonicity.
This algorithm resolves potential conflicts in multiple inheritance scenarios, particularly the 'diamond problem', where a class inherits from two classes that share a common ancestor.
classDiagram class object class A class B class C class D A <|-- B A <|-- C B <|-- D C <|-- D D --|> B : inherits D --|> C : inherits B --|> A : inherits C --|> A : inherits A --|> object : inherits note for D "MRO: D, B, C, A, object"
Class diagram illustrating the 'diamond problem' and its MRO resolution.
AttributeError
or TypeError
in complex inheritance hierarchies. Always check ClassName.mro()
when method resolution seems ambiguous.Practical Use Cases for mro()
Beyond debugging, mro()
can be useful for:
- Introspection: Understanding the internal structure of a class and its inheritance path.
- Metaclass development: When creating custom metaclasses, understanding the MRO is essential for correctly manipulating class creation.
- Framework development: Designing extensible frameworks where plugins or components might override methods from various base classes.
- Preventing unexpected behavior: Explicitly checking the MRO can help predict how method calls will resolve, especially when dealing with third-party libraries or legacy code.
class BaseService:
def process(self, data):
print("BaseService processing")
return data
class LoggingMixin:
def process(self, data):
print("LoggingMixin: Logging data before processing")
# Call the next method in the MRO
return super().process(data)
class ValidationMixin:
def process(self, data):
print("ValidationMixin: Validating data")
if not isinstance(data, dict):
raise ValueError("Data must be a dictionary")
# Call the next method in the MRO
return super().process(data)
class MyService(ValidationMixin, LoggingMixin, BaseService):
def process(self, data):
print("MyService: Custom processing logic")
# Call the next method in the MRO
return super().process(data)
service = MyService()
print(MyService.mro())
# Output will show: MyService, ValidationMixin, LoggingMixin, BaseService, object
service.process({"key": "value"})
# Expected output sequence:
# MyService: Custom processing logic
# ValidationMixin: Validating data
# LoggingMixin: Logging data before processing
# BaseService processing
Example demonstrating mro()
and super()
in a mixin-based architecture.
super()
function relies heavily on the MRO. It dynamically determines the next class in the MRO to call a method from, allowing for cooperative multiple inheritance.