Multiple Inheritance In Python with Examples

As we know, Python supports multiple inheritance which gives it a plus point over Java. Java doesn’t support multiple inheritance since it can be conflicting in some ways.

We’ll look at those conflicts and find ways to resolve them in this article. We’ll also learn about the Method Resolution Order (MRO).

MRO is the concept that works behind multiple inheritance in Python.

What is Multiple Inheritance in Python?

Multiple Inheritance is a type of inheritance in which one class can inherit properties(attributes and methods) of more than one parent classes.

A practical example would be You.

You may have inherited your eyes from your Mother and nose from your father. In Multiple inheritance, there is 1 child class inheriting from more than 1 parent classes.

multiple inheritance in Python

Here, the child class C inherits from 2 parent classes, A and B.

Let’s code it!

class hulk:
    def smash(self):
        return "I smash"

class banner:
    def speak(self):
        return "I've got the brains!"


class smarthulk(hulk, banner):
    pass

s1 = smarthulk()
print(s1.smash(), "and", s1.speak())

Output:

I smash and I’ve got the brain!
>>>

Let’s break this code down:

  • class smarthulk inherits from two parents classes, hulk and banner.
  • So smarthulk will have all methods and attributes of both its parent classes.
  • We then create an object s1 of class smarthulk and call the methods smash() and speak() on it.

Easy right?

But the question here is- what happens when both the parent classes have methods or attributes with the same name?

Conflicts with Multiple Inheritance

First, let’s see what happens when the parent classes have attributes with conflicting names.

class A():  
    def __init__(self):
        self.name = "Tony Stark"
   
class B():  
    def __init__(self):
        self.name = "Steve Rogers"

class C(A, B):  
    def __init__(self):
        A.__init__(self)
        B.__init__(self)

Both the parent classes, A and B have the attribute ‘name’.

Now if we make an object of class C, let’s see which parent class’s ‘name’ attribute it inherits.

>>> c1 = C()
>>> c1.name

Output:

‘Steve Rogers’
>>>

Even though class C inherits from both A and B, when we try to print the name attribute of the object of class C, we see ‘Steve Rogers’ instead of ‘Tony Stark’.

Now, this can be conflicting right?

This is because when C calls the __init__ method of class A, c1.name gets the value ‘Tony Stark’.

But then C calls the __init__ of class B and it overwrites the value of attribute c1.name and changes it to ‘Steve Rogers’.

Let’s now see what happens when both these parent classes have methods with the same name.

class A():  
    def __init__(self):
        self.name = "Tony Stark"

    def display(self):
        print("method of class A")
   
class B():  
    def __init__(self):
        self.name = "Steve Rogers"

    def display(self):
        print("method of class B")
  
class C(A, B):  
    pass

Here, both the parent classes A and B have methods with the name ‘display’.

Let’s see which version of the display method gets invoked when we call it on an object of class C.

>>> c1 = C()
>>> c1.display()

Output:

method of class A
>>>

This time, the method of class A gets invoked.

Let’s also see what happens when class C tries to override this method.

class A():  
    def __init__(self):
        self.name = "Tony Stark"

    def display(self):
        print("method of class A")
   
class B():  
    def __init__(self):
        self.name = "Steve Rogers"

    def display(self):
        print("method of class B")
  

class C(A, B):  
    def display(self):
        print("method of class C")

Let’s call the display method again.

>>> c1 = C()
>>> c1.display()

Output:

method of class C

Now, the interpreter calls display of C.

Losing your mind?

Well, this last discussion on Method Resolution Order(MRO) will make it perfectly clear to you.

Method Resolution Order

We have modified our previous code to get a deeper understanding of what’s happening with each __init__ call. Also, we have used super to invoke the __init__ method of the superclass of C.

class A():  
    def __init__(self):
        print("Starting __init__ of A")
        self.name = "Tony Stark"
        print("ending __init__ of A")

    def display(self):
        print("method of class A")
   
class B():  
    def __init__(self):
        print("Starting __init__ of B")
        self.name = "Steve Rogers"
        print("ending __init__ of B")
        
    def display(self):
        print("method of class B")
  

class C(A, B):
    def __init__(self):
        print("Starting __init__ of C")
        super().__init__()
        print("ending __init__ of C")
        
    def display(self):
        print("method of class C")

Test is in the Python shell:

>>> c1 = C()

Output:

Starting __init__ of C
Starting __init__ of A
ending __init__ of A
ending __init__ of C
>>> c1.name

Output:

‘Tony Stark’
>>> c1.display()

Output:

method of class C
>>>

So when we create a new instance of class C, it calls its constructor, i.e, the __init__ method of C.

The super in the __init__ of class C then calls the __init__ method of the superclass of C. Note that only the __init__ of A gets called and that of B remains untouched.

Why? To understand this let’s look at the MRO hierarchy.

>>> C.mro()

Output:

[<class ‘__main__.C’>, <class ‘__main__.A’>, <class ‘__main__.B’>, <class ‘object’>]

This shows the order in which MRO searches for a method in an inheritance hierarchy.

The MRO chain for C is: C -> A -> B

The super() method simply calls the method of the next class in the hierarchy. Since we had a super() method in the __init__ of C, the __init__ of the next class, i.e, A gets invoked.

If we had a super() in the __init__ of A, it would have called the __init__ of B. (Since B comes next in the MRO chain).

(Note: object is the parent class from which every class inherits by default in Python).

Let’s reason out how MRO takes place in Python:

  • In the case of multiple inheritance, the attribute/method is first looked up in the current class.
  • If the interpreter does not find the said attribute/method, then it searches for it in the next class in the MRO hierarchy.
  • If it fails to find it in any of the classes in the hierarchy, it spits out an error message saying there is no definition of the attribute/method you are looking for.
  • In case of multiple parent classes, MRO searchers in a depth-first order followed by a left-right path.

Now, getting back to our example code.

When we call the ‘name’ attribute on c1, the interpreter first searches for it in class C. When it doesn’t find the attribute, it searches for it in the next class in the MRO chain, i.e. A. And finally displays the attribute’s value once it finds it.

The same thing happens while calling the display method on c1.

The interpreter finds a definition of display method in class C and hence invokes class C’s version of display. In case it doesn’t find the method’s definition in C, it moves on and searches deeper in the hierarchy. This is how MRO works.

Conclusion

In this python article, we learned about the implementation of multiple inheritance in Python.

We also saw what conflicts you might come across while working with python multiple inheritance.

Then we learned how the interpreter uses the Method Resolution Order to determine which version of a method/attribute gets called in case of multiple inheritance.

Hope you got a clear understanding of what happens under the hood and how you can take advantage of multiple inheritance in your program.