Skip to content
Go back

Understanding Python __mro__ Method Resolution Order

Published:  at  06:30 AM

Python’s __mro__ (Method Resolution Order) is a crucial concept that determines how Python looks up methods in inheritance hierarchies. Let’s dive into what it is, why we need it, and see it in action.

🔍 What is mro?

__mro__ is a tuple that defines the order in which Python searches for methods and attributes in a class hierarchy. Every class has this attribute, showing its linearized inheritance path.

class Animal:
    pass

class Dog(Animal):
    pass

print(Dog.__mro__)
# (<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)

Python searches from left to right: first in Dog, then Animal, finally object.


🎯 Why Do We Need It?

The MRO solves the diamond problem in multiple inheritance - when a class inherits from multiple classes that share a common ancestor.

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):  # Diamond inheritance
    pass

d = D()
print(d.method())  # "B" - follows MRO
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

Without MRO, Python wouldn’t know whether to call B.method() or C.method(). The MRO provides a consistent, predictable order using the C3 linearization algorithm.


💡 Practical Use Cases

1. Super() in Cooperative Inheritance

super() follows the MRO, not just the parent class. This enables cooperative multiple inheritance:

class Logger:
    def save(self):
        print("Logging...")
        super().save()  # Continues MRO chain

class Database:
    def save(self):
        print("Saving to database...")

class Model(Logger, Database):
    def save(self):
        print("Validating...")
        super().save()  # Calls Logger.save()

model = Model()
model.save()
# Output:
# Validating...
# Logging...
# Saving to database...

2. Mixin Classes

MRO makes mixins powerful by ensuring methods are called in the right order:

class TimestampMixin:
    def save(self):
        self.updated_at = "2025-08-11"
        super().save()

class ValidationMixin:
    def save(self):
        if not hasattr(self, 'name'):
            raise ValueError("Name required")
        super().save()

class BaseModel:
    def save(self):
        print(f"Saving {self.name} at {self.updated_at}")

class User(ValidationMixin, TimestampMixin, BaseModel):
    def __init__(self, name):
        self.name = name

user = User("Alice")
user.save()  # Validates, adds timestamp, then saves
# Output: Saving Alice at 2025-08-11

3. Framework Method Overriding

Web frameworks like Django use MRO to let you override specific behaviors while keeping others:

class BaseView:
    def get(self):
        return self.render()
    
    def render(self):
        return "Base rendering"

class AuthMixin:
    def get(self):
        if not self.is_authenticated():
            return "Login required"
        return super().get()
    
    def is_authenticated(self):
        return False

class MyView(AuthMixin, BaseView):
    def render(self):
        return "Custom rendering"

view = MyView()
print(view.get())  # "Login required" - AuthMixin.get() runs first
print(MyView.__mro__)
# Shows: MyView -> AuthMixin -> BaseView -> object

🚀 Key Takeaways

Remember: when in doubt, check YourClass.__mro__ to see the exact lookup order!


Suggest Changes

Previous Post
The Dunning-Kruger Effect - Why Beginners Feel Like Experts
Next Post
How to patch vulnerable sub-dependencies with overrides in package.json