Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built-in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together. One of the popular programming paradigm used with Python language is Object Oriented Programming or OOP in short. One of the strong features of OOP is the support of Inheritance. The key to understanding Inheritance is that it provides code re-usability. In place of writing the same code, again and again, we can simply inherit the properties of one class into the other.
In this article, we will try to understand how Python support inheritance and resolve method or attribute using a simple example. As python is interpreted language we would be using examples to explain these concepts in an easy way. So let's get started.
What is inheritance?
Inheritance is the code reuse procedure by which one class can inherit the attributes and methods of another class. Inheritance models what is called an is a relationship. This means that when you have a Derived class that inherits from a Base class, you created a relationship where Derived is a specialized version of Base. The class whose properties are inherited is called parent class or base class and the class which inherits these properties from the parent or base class is called a child class or derived class.
The interesting part of the inheritance is that child class can not only inherit the methods and attributes of the parent class but a child class can have its own attributes and method.
Types of Inheritances
Object-Oriented Programming supports different types of inheritances. We can classify the inheritance into the below 5 types.
Single Inheritance
Multiple Inheritance
Multi-level Inheritance
Hierarchical Inheritance
Hybrid Inheritance
Inheritance gives the child class the flexibility to override certain methods to suit its own need and continue to use other inherited attributes and methods from the parent class.
The syntax used in Python to inherit the class is as follows:
class parent_class:
body of parent class
class child_class( parent_class):
body of child class
The below code snippets summarizes different inheritance supported in Python OOP.
Single Inheritance
# Parent class A
class A :
pass
# Child class B: Single Inheritance
class B(A):
pass
Multiple Inheritance
# Parent class A
class A :
pass
# Parent class B
class B :
pass
# Child class C: Multiple Inheritance
class C(A, B):
pass
Multilevel Inheritance
# Parent class A
class A :
pass
# Child class B: Single Inheritance
class B(A):
pass
# Child class C: Multilevel Inheritance
class C(B):
pass
Hierarchical Inheritance
# Parent class A
class A :
pass
# Child class B1
class B1(A):
pass
# Child class B2
class B2(A):
pass
Hybrid Inheritance
# Parent class A
class A :
pass
# Child class B1
class B1(A):
pass
# Child class B2
class B2(A):
pass
# Child class C: Hybrid Inheritance
class C(B1, B2):
pass
Now we've gone through the basics of inheritance, let's try to dwell inside to understand how Python handles the resolution of methods and attributes in the inheritance chain. Python uses Method Resolution Order or MRO, in short, to define the class search path to select the right method to use in class having multi inheritance. It is evolved from Python 2.2 to 2.3 and algorithm changes are referred to as old class MRO. Note, New classes are classes whose first parent inherits from the Python root « object » class and in this article, we will try to understand old class MRO.
Method Resolution Order(MRO) denotes the way a programming language resolves a method or attribute.
Understanding Method Resolution
Let's try to understand how MRO works in python. Consider below example code below where class B and class C are single inheritance, and class D is hybrid inheritance. All the classes have method who_am_i() defined and based on object type it calls appropriate who_am_i() method.
#example1.py
class A:
def who_am_i(self):
print("I am a A")
class B(A):
def who_am_i(self):
print("I am a B")
class C(A):
def who_am_i(self):
print("I am a C")
class D(B,C):
def who_am_i(self):
print("I am a D")
d1 = D()
d1.who_am_i()
If we execute the above program then we will get below output.
$ python example1.py
I am a D
Let's make this example bit interesting by removing the method who_am_i() from class D.
#example2.py
class A:
def who_am_i(self):
print("I am a A")
class B(A):
def who_am_i(self):
print("I am a B")
class C(A):
def who_am_i(self):
print("I am a C")
class D(B,C):
pass
d1 = D()
d1.who_am_i()
If we execute the above program then we will get below output. Call to who_am_i() method from class D object results in method from class B to execute.
$ python example2.py
I am a B
The reason for this is in Python, the child class first searches the method in its own class and if not found then it searches parent class from left to right order of inheritance.
In multiple inheritances, the child class first searches the method in its own class. If not found, then it searches in the parent classes depth_first and left-right order.
As opposed to languages like C++ which support OOP can inherit parent class virtually to avoid inheriting the same parent class twice, Python relies on MRO to select the right method at runtime. In order to inspect the MRO of a particular class, we can use below syntax:
class_name.mro()
# or
class_name.__mro__
For example, if we inspect the MRO of class D, it looks something like below.
It is a very simple and easy-to-understand MRO algorithm. When a class inherits from multiple parents, Python builds a list of classes to search for when it needs to resolve which method has to be called when one is invoked by an instance.
This algorithm is a tree routing, and works this way, deep first, from left to right :
Look if the method exists in the instance class
If not, looks if it exists in its first parent, then in the parent of the parent, and so on
If not, it looks if the current class inherits from other classes up to the current instance others parents.
Impossible Method Resolution
As the complexity of inheritance increases, it may lead to cases where Python fails to build a suitable method resolution path. In such cases, Python throws an exception TypeError: Cannot create a consistent method resolution. Let's try to use the below example to simulate the failure cases.
# impossible_inheritance.py
class X():
def who_am_i(self):
print("I am a X")
class Y():
def who_am_i(self):
print("I am a Y")
class A(X, Y):
def who_am_i(self):
print("I am a A")
class B(Y, X):
def who_am_i(self):
print("I am a B")
class F (A, B):
def who_am_i(self):
print("I am a F")
f1=F()
f1.who_am_i()
If we try to execute above program, it ends up with the error 'TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y'.
$ python impossible_inheritance.py
Traceback (most recent call last):
File "impossible_inheritance.py", line 17, in <module>
class F (A, B):
TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y
Summary
In this article, we've touched upon different types of inheritances supported in Python Object Oriented Programming and discussed how Python resolved method overloading at runtime. We've also discussed how complex inheritances can lead to exceptions where when Python could not build appropriate MRO.
Refernces
https://makina-corpus.com/
Comments