Method Resolution Order in Python

Method Resolution Order, or MRO, is the order in which Python looks for a method or an attribute in a class hierarchy. This is particularly crucial in multiple inheritance to ensure consistency and predictability in the resolution of methods.

Python uses the C3 Linearization Algorithm to calculate the MRO. This algorithm ensures that:

  1. Child classes are checked before their parent classes.
  2. The inheritance order is maintained.
  3. No ambiguities occur even in complicated inheritance scenarios.

Viewing MRO

Python provides the __mro__ attribute and mro() method to inspect the MRO of a class.

Example:

class A:
    pass

class B(A):
    pass

print(B.__mro__)
# OR
print(B.mro())

Output:

(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

Explanation:

  • The MRO starts with B, the class itself.
  • Then it moves to its parent A, and finally the base object class, which is the root of all classes in Python.

MRO with super()

The super() function uses MRO to determine the next class in line to delegate method calls.

Example:

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

class B(A):
    def method(self):
        print("B method")
        super().method()

b = B()
b.method()

Output:

B method
A method

Explanation:

  • The method() in B is called first.
  • super().method() in B calls the method() of A, as per the MRO.

Multiple Inheritance Example

When a class inherits from multiple parent classes, Python resolves the order using MRO.

Example:

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

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

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

class D(B, C):
    pass

d = D()
d.method()
print(D.__mro__)

Output:

B method
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

Explanation:

  • D inherits from B and C.
  • MRO is: D → B → C → A → object.
  • The method() in B is called first because B appears before C in D(B, C).

Rules of C3 Linearization

1. Child Before Parent:

Always checks the child class before its parent classes.

2. Preserve Inheritance Order:

If a class inherits from several classes, then it follows the order defined by the inheritance tuple.

3. Avoid Conflicts:

The algorithm eliminates any possibility of ambiguity of method resolution; specifically, the case of diamond-shaped inheritance.

Real-World Example

Example:

class Animal:
    def speak(self):
        print("Animal speaks")

class Mammal(Animal):
    def speak(self):
        print("Mammal speaks")

class Bird(Animal):
    def speak(self):
        print("Bird chirps")

class Bat(Mammal, Bird):
    pass

bat = Bat()
bat.speak()
print(Bat.__mro__)

Output:

Mammal speaks
(<class '__main__.Bat'>, <class '__main__.Mammal'>, <class '__main__.Bird'>, <class '__main__.Animal'>, <class 'object'>)

Explanation:

  • Bat inherits from both Mammal and Bird.
  • MRO: Bat → mammal → bird → animal → object.
  • The speak() method in Mammal is invoked first because, in the MRO, Mammal appears before Bird.

Diamond Inheritance

Example:

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

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

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

class D(B, C):
    def method(self):
        print("D method")
        super().method()

d = D()
d.method()
print(D.__mro__)

Output:

D method
B method
C method
A method
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

Explanation:

  • MRO resolves the diamond pattern without ambiguity:
    D → B → C → A → object.

Key Takeaways

1. Deterministic MRO:

It always returns a definite order of resolving methods.

2. C3 Linearization ensures no conflicts:

Even in the diamond situation, MRO does not cause ambiguity.

3. super() simplifies delegation of methods:

You do not need to hardcode names of the parent classes because super() will automatically find the next class in MRO.

4. object is the ultimate base class:

All Python classes are subclasses of object. It is the last in the MRO.