Multiple inheritance and MRO in python

Multiple inheritance and MRO in python

Your missing guide to multiple inheritance and Method Resolution Order in python.

Introduction

Let's first talk about the word inheritance, oxford dictionary defines it as something from the past or from your family that affects the way you behave, look, etc.

In object-oriented programming, inheritance facilitates code reuse with the primary motivation of reusing or extending the parent or base class in children or subclass, hence the subclass or children class is affected by the parent class. It however provides freedom to children class to modify the parent's attributes and methods.

Inheritance

Let's see an example which is most common in all paradigm, here Children class inherits from Parent class and inherits some methods of Parent class and also overrides the Parent class implementation:

class Parent():
    def parent_function(self):
        print("parent_function")

    def common_function(self):
        print("Parent's common_function")


class Children(Parent):
    def child_function(self):
        print("Child's child_function")

    def common_function(self):
        print("Child's common_function")

if __name__ == "__main__":
    child = Children()
    parent = Parent()

    parent.parent_function() # #prints 'parent_function'

    child.child_function()  #prints 'child's function'
    child.parent_function() #prints 'parent_function'

    # common of both
    parent.common_function() #prints "Parent's common_function"
    child.common_function() #prints "Child's common_function"

This type of single inheritance is common across all Object-Oriented Paradigm, However, Python has something more to offer with multiple inheritance, which means you can inherit from multiple base classes.

Multiple Inheritance

Multiple Inheritance refers to the inheritance that uses two or more base class. This is how you define multiple inheritance using class keyword for multiple baseClass:

class SubClass(baseClass, baseClass2, baseClass3, ....)

Python has its renowned multiple inheritance feature which comes in handy when working with a class that needs implementation from two or more class.

When working with multiple base class there arises confusion if one or more base class implements the same method. Let's see how this is handled in python in next step:

Method Resolution Order

Multiple inheritance can create confusion when there are one or more base classes that implement the same method. Python(2.3 and newer) version introduced the renowned method resolution order or mro to deal with the method resolution while using multiple inheritance.

Method resolution order(MRO) in short has its root in C3 linearization algorithm which basically outlines the basis for MRO list calculation in python multilevel inheritance. Method Resolution Order is the python provided calculation of inheritance graph.

The MRO calculation has 3 major points to consider for the method resolution list from a subclass.

  • Subclasses comes first rather than the base classes
  • Base class definition order is preserved
  • Fits the above two criteria for all MRO calculation in a program

If this is violated then C3 prohibits the python from inheritance declaration.

Let's see an example to see the mro list.

class A():
    def __init__(self):
        print("Hi from A")

class B(A):
    def __init__(self):
        print("Hi From B")

class C(A):
    def __init__(self):
        print("Hi from C")

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


print(D.__mro__) 
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(E.__mro__) 
# ((<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>))

We use the __mro__ attribute often pronounced as dun~der mro (double underscore mro) with class name, It gives the tuple of classes defining the method's resolution order for the subclass.

In the above example, we can see the diamond inheritance formation. So, on printing D.__mro__ attribute, it gives:

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

and on printing E.__mro__ , it gives:

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

Notice the difference in the second element of the above tuple, its because the base class order is preserved for the class definition of class D and E: D(B, C) and E(C, B).

So, <class '__main__.C'> in the second index for the E subclass and <class '__main__.B'> in the second index for B subclass.

The method resolution order will be entirely based on the above tuple order,
which means when any method is invoked for the instance of class D, the method of class D will be resolved first and if not found it will try to resolve from the second element from the D.__mro__ tuple , which is class B and so on to the next element, if no such method is implemented it will result in AttributeError.

If the implementation is found for the current lookup the search stops right there and the method will be resolved from there.

If you made it upto here, Please come up in discussion to check the __mro__ for the below snippet.

class A():
    def __init__(self):
        print("Hi from A")

class B(A):
    def __init__(self):
        print("Hi From B")

class C(A):
    def __init__(self):
        print("Hi from C")

class D(B, A, C):
    pass

Why?

Finally, If you learned something from this article, please share it with your friends.

You may also follow me on LinkedIn and Twitter .

Did you find this article valuable?

Support Shiva Gaire by becoming a sponsor. Any amount is appreciated!