Summary: In this tutorial, we will learn what method resolution order (MRO) is and what role it plays in Python inheritance.
What is MRO in Python?
MRO is the order in Python in which a method is looked for in the class hierarchy.
It is a sequence that Python uses to find a method in a class.
It is mainly useful in the case of inheritance to look for a method in the parent classes.
Consider the following inheritance program for instance:
class A:
def display(self):
print('display method')
class B(A):
pass
b = B()
b.display() #output: display method
In this program, we are calling the display
method on the instance of class B
i.e. b.display()
.
The Python interpreter first looks for this method in class B
, not getting the method, it searches for it in the parent class i.e class A
, and invokes it.
Here, the Python interpreter uses the MRO to decide what class is next to find the method.
How to access MRO in Python?
Every class that we write in our Python program, whether it is involved in inheritance or not, has its corresponding MRO.
We can see the MRO by accessing the __mro__
attribute or invoking the mro()
method on the class name.
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
As we can see, class A
is later in order than class B
, so when the interpreter does not find the method in class B
it looks for the method in class A
.
Example of MRO in Multiple Inheritance
In the above example, we were certain that the display
method is in class A
, so it is the one that is invoked.
But there are some cases in which without looking at the __mro__
it may become ambiguous for us to be sure of the method referenced by the Python.
Consider the following inheritance example in Python:
class A:
def display(self):
print('display method')
class B:
def display(self):
print('display method')
class C(B, A):
pass
c = C()
c.display() #output: display method
Here, both class B
and class A
are the parent classes of class C and both have the similar display
method.
Hence, from the output, it is unclear which display
method has been called by Python.
To be sure of this, we output the method resolution order of class C i.e. C.__mro__
and see which class appears before in the order.
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
As class B
is before class A
in the order, we are now sure that the display method of class B
is called by the interpreter.
This ambiguity in the resolution of the method in case of multiple inheritance is popularly known as diamond problem.
To resolve such ambiguity Python uses MRO.
Exception in Creating MRO
The sequence of classes in the MRO is decided on the basis of the C3 linearization algorithm.
However, in some cases, the algorithm may fail to produce consistent method resolution order.
Consider the following inheritance program in Python:
class A:
def display(self):
print('display method')
class B:
pass
class C(B, A):
pass
class D(B, C):
pass
d = D()
d.display()
Output:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases B, C
In this example, Python throws an error stating that it cannot create a consistent MRO.
In Python, the MRO of the parent classes has to be consistent i.e. its MRO cannot be changed and strictly used as it is by the child classes to create their respective MRO.
If we look into the MRO of class C, class B comes after class C i.e. C->B->A->object
.
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
Since class B is also the immediate parent class of class D, the MRO for class D must have class B followed by the MRO of class C i.e D->B->[MRO of C]
, which is equivalent to D->B->C->B->A->object
.
Because MRO of class D
tries to create an order where class B
comes before class C
, it fails to produce consistent MRO.
We can resolve this error by changing the inheritance order from class D(B, C)
to class D(C, B)
.
By doing so, class D
follows the MRO of its parent class C
i.e. class B
comes after class C
.
class A:
def display(self):
print('display method')
class B:
pass
class C(B, A):
pass
class D(C, B):
pass
d = D()
d.display()
Output:
display method
If we look at the MRO of class D
, we see the order remains consistent.
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)