Close
Close

Python Method Overriding and MRO


In the previous chapters, we read about inheritance and the different types of inheritance that can be implemented in Python.

Now, let’s consider a scenario where the parent and the child class have methods with the same name. In such cases, method overriding takes place. 

Python Method Overriding


To understand method overriding, let's first look at an example.

class Animal():
    def sound(self, a):
        print("This is parent class")

class Dogs(Animal):
    def sound(self):
        print("Dogs bark")

d = Dogs()
d.sound()
Output
Dogs bark

In the above example, the class Dogs and its parent class Animal have methods with the same name sound. When the object d of the class Dogs calls this method, then the method of the child class Dogs gets called, not that of the parent class. Thus, the method of the child class overrides the method of the parent class when called by an object of the child class.

This is called method overriding.

We saw the use of super() while learning about subclass. Let’s call a parent class method inside a child class method using super(), though this is not related to method overriding.

class Shape():
    def display(self):
        print("This is Shape")
		
class Rectangle(Shape):
    def display(self):
        print("This is Rectangle")
        super().display()
		
class Square(Rectangle):
    def display(self):
        print("This is Square")
        super().display()
		
sq = Square()
sq.display()
Output
This is Square
This is Rectangle
This is Shape

The class Shape has a subclass Rectangle and Rectangle has a subclass Square. All the three classes have methods with the same name. The display() method of Square calls the display() method of its parent Rectangle using super() as super().display(). The display() method of Rectangle calls the display() method of its parent Shape by calling super().display().

That’s it. So this was all about method overriding. 

Now consider a scenario where a class inherits two parent classes and both the parent classes have a method with the same name, say func. If an object of the child class calls this method func(), then according to you the method of which of the two parent classes must be called?

This seems confusing. For these types of scenarios, there is an order in which Python calls the variables (attributes) and methods of classes in inheritance. This order is known as Method Resolution Order (MRO).

Python Method Resolution Order (MRO)


Before looking at the rules of MRO, let’s introduce a new term - object class.

Python object Class


In Python, every class is a subclass of the object class. The object class is an in-built class in Python and is the superclass of all the classes.

In Python, each class is a subclass of the object class.

Let’s prove it with an example.

class Shape():
    pass

s = Shape()

print(issubclass(Shape, object))
print(isinstance(s, object))
Output
True
True

Here we created a class Shape and its object s. So, as we mentioned above, Shape is a subclass of the in-build object class. Therefore, issubclass(Shape, object) returned True and isinstance(s, object) also returned True.

In the chapter Datatypes, we saw that the data types (like int, float, list, etc.) are predefined classes in Python. For example, int is a pre defined class and integers like 4, 10, etc, are its objects. This also means that all the data types are also subclasses of the object class. We can verify this using the issubclass() and isinstance() functions as shown below.

print(issubclass(int, object))
print(issubclass(float, object))
print(isinstance(5, int))
print(isinstance(5, object))
Output
True
True
True
True

As discussed, data types int and float are subclasses of the object class.

In fact it is a good practice to inherit object class while making our own class. For example instead of class Square(), we can and should write class Square(object).

class Shape(object):
    pass

s = Shape()

Python Rules of Method Resolution Order


There are two rules followed by Python to achieve MRO - depth-first path rule and left-right path rule.

Python Depth-first path Rule


This rule states that a method is first searched in the current class. If the method is not found in the current class, then it is searched in the parent class.

We have already looked at this case in method overriding. Let’s again see an example.

class A():
    def display(self):
        print("This is A")

class B(A):
    def display(self):
        print("This is B")

b = B()
b.display()
Output
This is B

This is the simplest example of method overriding. 

Here B is a subclass of A and both the classes have the display() method.  An object of B calls the display() method. 

So, according to the depth-first path rule, the method is first searched in the class whose object calls it. Therefore, the display() method is searched in class B and executed.

Now in the above example, assume that this method is not present in class B as shown below.

class A():
    def display(self):
        print("This is A")

class B(A):
	pass

b = B()
b.display()
Output
This is A

According to the rule, if the method is not present in the class whose object called it, then the method is searched in the parent class. Therefore the display() method is searched in the parent class A of the class B and executed.

Look at another example below.

class A():
    def display(self):
        print("This is A")

class B(A):
	pass
		
class C(B):
	pass

c = C()
c.display()
Output
This is A

This is the same as previous examples, with an additional level of inheritance. C is a subclass of B and B is a subclass of A.

So, if the object of C calls a method, then it is first searched in C, then in B and then in A. So, the order is C → B → A.

We can also check this order using the __mro__ attribute or the mro() function.

For example, the order in which classes are checked when an object of the class C calls a method can be found using C.__mro__ or C.mro(). Let’s print the MRO in the above example.

class A():
    def display(self):
        print("This is A")

class B(A):
    pass
		
class C(B):
    pass

c = C()
c.display()

print(C.__mro__)
print(C.mro())
Output
This is A
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

And we got the same order: C → B → A → object. You can ignore the object class at the end, because we will always get it at the end of an MRO.

So this was the depth-first path rule, though we already learned about it in the method overriding section.

Python Left-right path Rule


Now consider a case where a class has two parent classes and all the three classes have methods with the same name.

class A():
    def display(self):
        print("This is A")

class B():
    def display(self):
        print("This is B")
		
class C(A, B):
    def display(self):
        print("This is C")

c = C()
c.display()
Output
This is C

In this example, C is a subclass of two classes A and B and all the three classes have the display() method. 

When the object c of the class C calls the display() method, then no doubt this method is first checked in class C (because the class whose object calls the method is checked first). 

If the method was not present in C, then according to the depth-first path rule, it is checked in the parent class of C. But we have two parent classes of C, so which one is checked first?

This is where the left-right path rule is followed. This rule states that if we have two parent classes of a class, then the parent classes are checked in the order in which they are inherited.

For example, in the above program, C inherits A and B as class C(A, B). This means that it inherited first A and then B, and therefore out of the two parent classes, A is checked before B. Let’s verify this by displaying the MRO as shown below.

class A():
    pass

class B():
    pass
		
class C(A, B):
    pass

print(C.mro())
Output
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

We got the same order which we were expecting: C → A → B.

Let’s check this in practice in the following example.

class A():
    def display(self):
        print("This is A")

class B():
    def display(self):
        print("This is B")
		
class C(A, B):
	pass

c = C()
c.display()
Output
This is A

Since the object of C calls the method, first the method is checked in C. 

The method is not present in C, therefore it is checked in one of the parent classes of C according to the depth-first path rule. 

Out of the two parents, the method is checked first in A according to the left-right path rule. The method is present in A and therefore the method of A got executed.

So these are the two rules which Python follows to detect which class’ method to call in inheritance. Also, remember that in case of collision, depth-first path rule is preferred over left-right path rule.

Precedence of depth-first path rule is higher than left-right path rule.
A method can’t be checked in a class more than once.
A class can’t be checked before its direct subclass in MRO.

Let’s look at some more examples of MRO.

Example 1

class A():
    pass

class B():
    pass

class C(A, B):
    pass

class D(C):
	pass

print(D.mro())
Output
[<class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

This one was easy. Here we are checking the MRO of class D, which means that the method called by an object of D will be checked in the classes in which order.

We got the MRO as D → C → A → B.

The method is checked first in D because its object called the method. 

If the method is not present in D, then it is checked in its parent class C (depth-first path rule). 

If it is not present in C, then it is checked in of the parent classes of C (depth-first path rule). 

Out of the two parents, the method is checked in first A and then in B (left-right path rule).

Example 2

class A():
    pass

class B(A):
    pass

class C():
    pass

class D(B, C):
    pass

print(D.mro())
Output
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]

We got the MRO as D → B → A → C

If an object of D calls the method, then it is first checked in D. 

If the method is not present in D, then it is checked in one of the parent classes of D (depth-first path rule). 

Out of the two parents, the method is checked first in B (left-right path rule).

If the method is not present in B, then according to the depth-first path rule it should be checked in its parent class A, but according to the left-right path rule it should be checked in C. Since the precedence of depth-first path rule is higher than left-right path rule, so the method will be checked in A (depth-first path rule).

If it is not present in A, then it is checked in C.

Example 3

class A():
    pass

class B():
    pass
	
class C(A):
    pass
	
class D(B, C):
    pass

print(D.mro())
Output
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

We got the MRO as D → B → C → A

First D is checked. 

If the method is not present in D, then one of its parents (B or C) is checked (depth-first path rule). 

Out of the two parents, the method is checked first in B (left-right path rule).

If the method is not present in B, then it is checked in C (left-right path rule).

If it is not present in C, then it is checked in the parent of C i.e. A (depth-first path rule).

Exmaple 4

class A():
    pass

class B(A):
    pass
	
class C(A):
    pass
	
class D(B, C):
    pass

print(D.mro())
Output
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

We got the MRO as D → B → C → A

If you calculate the MRO on your own, maybe you will get the answer as D → B → A → C → A. Let’s understand why we got a different MRO.

First the method is checked in class D.

If the method is not found in D, then it is searched in one of its parents (B or C) (depth-first path rule).

Out of the two parents, the method is checked first in B (left-right path rule).

If the method is not found in B, then according to the higher precedence of depth-first path rule, it should be checked in A and not in C. But we also read that a class can’t be checked before its direct subclass. Since C is a direct subclass of A, therefore C is checked before A.

Now, if the method is not found in C, then it is checked in A.

Example 5

class A():  
    pass


class B():  
    pass


class C(A, B):  
    pass


class D(B):  
    pass

class E(C,D):  
    pass

print(E.mro())
Output
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.B'>, <class 'object'>]

We got the MRO as E → C → A → D → B

Try to understand how we got this MRO on your own. If stuck, you can ask doubts in the discussion section.

Congratulations! You have just finished the Object Oriented Programming part of Python. This is a big milestone for anyone learning this language and you can now confidently call yourself a Python programmer. It is also important to practice a lot of questions to become better in Python.

In the next chapters, we will be looking at some more concepts which a good Python developer should know.

To learn from simple videos, you can always look at our Python video course on CodesDope Pro. It has over 500 practice questions and over 20 projects.
Reality is created by the mind, we can change our reality by changing our mind.
- Plato


Ask Yours
Post Yours
Doubt? Ask question