×

Inheritance in OOPS – Python

Inheritance is an important concept in object-oriented programming. Inheritance means deriving a new class from an existing class. A class that inherits the properties is known as child class and the class whose properties are inherited is known as parent class(also called base class, superclass).

All the variables, methods, and constructors in the parent class are by default available to the child class and we don’t have to re-define them in the child class.

Hence the main advantage of Inheritence is the code re-usability and also adding few or no features to the existing functionality.

The syntax of implementing the inheritance is passing the parent class into child class using parenthesis while declaring the child class.

class childclass(parent class):

The below example is a basic program that implements the inheritance concept. We can directly access the members of the parent class using the object of the child class.

class parent:
    value_1 = 50
    value_2 = 40
    def m1(self):
        print("parent method m1")
    @classmethod
    def m2(cls):
        print("parent method m2")
    @staticmethod
    def m3():
        print("parent method m3")

class child(parent):
    pass
obj = child()
obj.m1()
obj.m2()
obj.m3()
print(obj.value_1, obj.value_2)

# Output
parent method m1
parent method m2
parent method m3
50 40

We can inherit all the members of the parent class except the constructor method( __init__ ). Because the constructor of the parent class will be executed only if the object of the parent class is created.

class parent:
    value_1 = 50
    def __init__(self):
        self.value_2 = 30
        self.value_2 = 40
    def m1(self):
        print("parent method")
class child(parent):
    def __init__(self):
        self.m1()
obj = child()
print(obj.value_1)
print(obj.value_2, obj.value_3)

# Output
parent method
50

AttributeError: 'child' object has no attribute 'value_1'

To access the data present in the constructor of the parent class from the child class the constructor method has to be called from the child class.

To overcome this we can use the super() keyword and can call the constructor of the parent class. Using the super() keyword we can call the members of the parent class into the child class.

class company:
    def __init__(self):
        self.company_name = "Python Software"
        self.company_established = "2005"
    def bonus(self):
        print("bonus this month")
        self.bonus = 10000

class employee(company):
    def __init__(self, emp_name, emp_age):
        super().__init__()
        self.emp_name = emp_name
        self.emp_age = emp_age
    def emp_details(self):
        print(f"{self.company_name} established in {self.company_established} has given 
                  {self.bonus} bonus to {self.emp_name} with age {self.emp_age}")

obj = employee("john", 54)
obj.bonus()
obj.emp_details()
print(obj.__dict__)

# Output
bonus this month
Python Software established in 2005 has given 10000 bonus to john with age 54
{'company_name': 'Python Software', 'company_established': '2005', 
'emp_name': 'john', 'emp_age': 54, 'bonus': 10000}

Types of Inheritance

Inheritance is classified into different types based on the number of classes being inherited and the chain of inheritance. They are:-

  • Single Inheritance
  • Multi-Level Inheritance
  • Hierarchical Inheritance
  • Multiple Inheritance
  • Hybrid Inheritance
  • Cyclic Inheritance

Single Inheritance

If a parent class is inherited by a single child class then the relation between the parent and child classes is known as single inheritance.

If Else 1 1
class parent:
    def m1(self):
        print("parent method")
class child(parent):
    def m2(self):
        print("child method")
obj = child()
obj.m1
obj.m2

# Output
parent method
child method

Multi-Level Inheritance

The concept of inheriting the members from multiple classes into a single class is known as multi-level inheritance. In multi-level inheritance, the child class can be a parent class to another child class and the process goes on.

Thus multi-level inheritance can also be called multiple level inheritance.

If Else 2 3

In the below example, the child class is inheriting the parent class so all the members of the parent class are also accessible by the child class. The child class is inherited by child2 class which makes it accessible to the members of both child class and parent class.

class parent:
    def m1(self):
        print("parent method")
class child(parent):
    def m2(self):
        print("child method m2")
class child2(child):
    def m3(self):
        print("child method m3")
obj = child2()
obj.m1()
obj.m2()
obj.m3()

# Output
parent method
child method m2
child method m3

Hierarchical Inheritance

In Hierarchical Inheritance one parent class is inherited by multiple child classes that are at the same level are known as Hierarchical inheritance.

In hierarchical inheritance, a single parent class will have multiple child classes which are at the same level and the members of the parent class are accessible to all the child classes.

If Else 3 3

In the below example, we are implementing Hierarchical Inheritance for the parent class by inheriting the parent class for two child classes that are at the same level. In this type of inheritance the child1 members are not accessible by child2.

class parent:
    def m1(self):
        print("parent method")
class child1(parent):
    def m2(self):
        print("child method m2")
class child2(parent):
    def m3(self):
        print("child method m3")
obj1 = child1()
obj1.m1()
obj1.m2()
obj2 = child2()
obj2.m1()
obj2.m3()

# Output
parent method
child method m2
parent method
child method m3

Multiple Inheritance

If a single child class inherits two or more classes then the relation between the child class and the multiple parent classes is known as Multiple Inheritance. All the members of the classes that are being inherited are accessible by the child class.

The parent classes that the child class inherits are declared in an order using parenthesis.

class childclass(parent class1, parent class2, parent class3, ....):
If Else 4 2

In the below example, we are implementing Multiple inheritence by inheriting the parent classes parent1 and parent2 into child class. The members of parent1 and parent2 are accessible by the child class.

class parent1:
    def m1(self):
        print("parent method m1")
class parent2:
    def m2(self):
        print("parent method m2")
class child(parent1, parent2):
    def m3(self):
        print("child method m3")
obj = child()
obj.m1()
obj.m2()
obj.m3()

# Output
parent method m1
parent method m2
child method m3

If the parent classes parent1 and parent2 has methods with the same name but different functionality then the order of passing the parent classes into child class is considered.

  • If we pass first parent1 and second parent2 and if there are any common methods between these both parent classes and if we access them from child class then method from parent1 is called.
  • If we pass first parent2 and second parent1 and if there are any common methods between these both parent classes and if we access them from child class then method from parent2 is called.
class parent1:
    def m1(self):
        print("parent1 method executed")
class parent2:
    def m1(self):
        print("parent2 method executed")

class child(parent1, parent2):
    pass
obj = child()
obj.m1()

# Output
parent1 method executed
class parent1:
    def m1(self):
        print("parent1 method executed")
class parent2:
    def m1(self):
        print("parent2 method executed")

class child(parent2, parent1):
    pass
obj = child()
obj.m1()

# Output
parent2 method executed

Hybrid Inheritance

If a class has different types of inheritance implemented in it, then this inheritance is known as Hybrid inheritance. As the name itself suggests there will be two or more inheritances implemented.

In hybrid inheritance to access the parent class members Method Resolution Order(MRO) is implemented.

If Else 5 1

Cyclic Inheritance

Implementing Inheritance such that one class inherits the other class in a cyclic order is known as Cyclic Inheritance. Python does not support Cyclic Inheritance.

Slicing 4
class A(A):
    pass

# Output
NameError: name 'A' is not defined
class A(B):
    pass
class B(A):
    pass

# Output
NameError: name 'B' is not defined

Method Resolution Order( MRO )

In the Hybrid Inheritance Method Resolution Order is implemented. It defines the class search path used by Python to search for the right method to use in classes having multi-inheritance.

Suppose we have a method that is present in multiple parent classes and if we call the method from the object of the child class, the search path for the method is defined by MRO.

Method Resolution with single, Multiple, Multi-level, Hierarchical inheritances

In the below example, Multiple inheritance is implemented as the child class inherits the parent classes parent_1 and parent_2.

class parent_1:
    pass
class parent_2:
    pass
class child(parent_1, parent_2):
    pass

If we call the method from the object of the child class, then the order in which the python searches for the method among the classes is child, parent_1, parent_2, object class.

  • Firstly Python looks for the method in the child class
  • If the method is not found resumes the search to parent_1 class in the order
  • If still the method is not found resumes the search to next class in the order i.e., parent_2 class
  • And finally if the method not found among all the classes then object class is searched for the method.
  • If method not found in object class then AttributeError is returned
# method_1 in child
# returned based on 
# the order
# C -> A -> B

class A:
    def method_1(self):
        print("class A method")
class B:
    def method_1(self):
        print("class B method")
class C(A, B):
    def method_1(self):
        print("class C method")
obj = C()
obj.method_1()

# Output
class C method

--------------------------

# method_1 in class A
# returned based on 
# the order
# C -> A -> B

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

obj = C()
obj.method_1()

# Output
class A method

--------------------------

# method_1 in class B
# returned based on 
# the order
# c -> B -> A

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

obj = C()
obj.method_1()

# Output
class B method

The method resolution is simple when the inheritance is of only a single type( either of the single, Multiple, Muti-Level, Hierarchical). If the Hybrid inheritance is involved then the method resolution is defined based on the MRO formula.

MRO in Hybrid Inheritance

Method Resolution Order also known as the C3 algorithm is proposed by Samuele Pedroni. It follows DLR(depth-first left to right) approach.

In DLR approach:-

  • depth is given more priority, which means child is given more priority than parent
  • left to right is given more priority if the nodes are at same level, which means left parent is given more priority than right parent

Formula for MRO of class X:

MRO(X) = X + Merge(MRO(Parent_1), MRO(Parent_2), MRO(Parent_3), ......., [list of parents])

Consider a class as an employee and MRO(employee) is calculated by employee + merge(mro's(inherited parents), list of parents)

The merge() function only accepts the parents that are directly inherited by the child class, not further than that.

Finding MRO by C3 Algorithm

  • Assuming C1, C2, C3, C4, C5…. as classes, and obviously all these classes are inherited from object class
  • Assuming class X has parent classes C1, C4, C5
  • Which means MRO(X) = X + Merge( MRO(C1), MRO(C4), MRO(C5), [C1, C4, C5] )
  • C7, C9, C3 are obviously inherited from object class
  • If MRO(C1) = C1C3C7O, then C1 is the head part and C3C7O is the tail part
  • If MRO(C4) = C4C6C9O, then C4 is the head part and C6C9O is the tail part
  • If MRO(C5) = C5C2C3O, then C5 is the head part and C2C3O is the tail part
  • Then the formula for Merge = MRO(C1), MRO(C4), MRO(C5), [C1, C4, C5] -> Merge(C1C3C7O, C4C6C9O, C5C2C3O, [C1C4C5])
  • While Calculating Merge, we need to take the first element from the first item of the list which is also called head part.
  • If this element is not available in the body part of other items of the list then its is added to MRO and removed from the list wherever it is present. If the element is available in body part of the list then next item is considered.
  • If the head part of the item is available as body element in other item then next item is considered and same process is repeated

Calculating Merge

From the calculations, we got Merge = C1C3C7O, C4C6C9O, C5C2C3O, C1C4C5.

To calculate Merge() we need to take the head part from the first item in the list i.e C1 from C1C3C7O and look for C1 in the tail part of other items. If C1 is not there in the Tail part of other items then C1 is added to the X. After adding the C1 to X we remove C1 from all list items.

Note:- Here ‘O‘ is the object class

MRO(X) = X + Merge(C1C3C7O C4C6C9O, C5C2C3O, C1C4C5)
MRO(X) = X + C1 + Merge(C3C7O, C4C6C9O, C5C2C3O, C4C5)

Now the head part of C3C7O is C3 and the body part is C7O. Thus considering C3 we search for C3 in other items’ body parts. Since C3 is an element in the body part of C5C2C3O, we skip the search and consider the next item C4C6C9O

MRO(X) = X + Merge(C1C3C7O C4C6C9O, C5C2C3O, C1C4C5)
MRO(X) = X + C1 + Merge(C3C7O, C4C6C9O, C5C2C3O, C4C5)

The head part of C4C6C9O is C4 and the body part is C6C9O, since there is no C4 as an element in the body part of other items it is added to the MRO and removed from the list.

MRO(X) = X + Merge(C1C3C7O C4C6C9O, C5C2C3O, C1C4C5)
MRO(X) = X + C1 + C4 + Merge(C3C7O, C6C9O, C5C2C3O, C5)

Now the head part of the remaining C6C9O is C6 and the body part is C9O, and the search is repeated for C6 in body parts of other items. This process is repeated till the last item is searched and the items are added to MRO.

MRO(X) = X + Merge(C1C3C7O C4C6C9O, C5C2C3O, C1C4C5)
MRO(X) = X + C1 + C4 + C6 + Merge(C3C7O, C9O, C5C2C3O, C5)

#C9O with C9 as head and O as body
MRO(X) = X + C1 + C4 + C6 + C9 + Merge(C3C7O, O, C5C2C3O, C5)

#O as head but present in other body parts so next item 
# C5C2C3O with C5 as head C2C3O as body
MRO(X) = X + C1 + C4 + C6 + C9 + C5 + Merge(C3C7O, O, C2C3O)
MRO(X) = X + C1 + C4 + C6 + C9 + C5 + C2 + Merge(C3C7O, O, C3O)
MRO(X) = X + C1 + C4 + C6 + C9 + C5 + C2 + C3 + Merge(C7O, O, O)
MRO(X) = X + C1 + C4 + C6 + C9 + C5 + C2 + C3 + C7 + Merge(O, O, O)
MRO(X) = X + C1 + C4 + C6 + C9 + C5 + C2 + C3 + C7 + O

# Output
MRO(X) = XC1C4C6C9C2C3C7O

Now let’s try a practical example in company class and find the MRO of the Employee.

Slicing 1 2
MRO(X) = X + Merge(MRO(Parent_1), MRO(Parent_2), MRO(Parent_3), [list of parents])

MRO(employee) = employee + Merge(MRO(supervisor_1), MRO(supervisor_2), MRO(department_3), [supervisor_1, supervisor_2, department_3])

Let’s optimize the naming conventions to make use of space in the website effectively.

employee     = e
supervisor_1 = s1
supervisor_2 = s2
department_1 = d1
department_2 = d2
department_3 = d3
company      = c
Object class = o

MRO(e) = e + Merge(MRO(s1), MRO(s2), MRO(d3), [s1, s2, d3])

MRO(s1) = s1, d1, d2, c, o
MRO(s2) = s2, d2, d3, c, o
MRO(d3) = d3, c, o

MRO(e) = e + Merge(s1d1d2co, s2d2d3co, d3co, s1s2d3)

Now, the head of the first element s1d1d2co is s1 and the body is d1d2co. We need to search for s1 in the body part of other items. Since there is no s1 in the other items body we need to add it to MRO and remove it from the merge list. We need to continue the process till the end

MRO(e) = e + s1 + Merge(d1d2co, s2d2d3co, d3co, s2d3)

# d1 head and no d1 found in other body
MRO(e) = e + s1 + d1 + Merge(d2co, s2d2d3co, d3co, s2d3)

# d2 head found in s2d2d3co
# next item considered is s2d2d3co
# Now s2 head
MRO(e) = e + s1 + d1 + s2 + Merge(d2co, d2d3co, d3co, d3)

# d2 head..no d2 found in body of other items
MRO(e) = e + s1 + d1 + s2 + d2 + Merge(co, d3co, d3co, d3)

# d3 head..no d3 found in body of other itmes
MRO(e) = e + s1 + d1 + s2 + d2 + d3 + Merge(co, co, co)

# next head is c
MRO(e) = e + s1 + d1 + s2 + d2 + d3 + c + Merge(o, o, o)

# next head o added to MRO
# final MRO 
MRO(e) = e + s1 + d1 + s2 + d2 + d3 + c + o
MRO(e) = e, s1, d1, s2, d2, d3, c, o

Thus the elaborated MRO of employee class is MRO(employee)

MRO(employee) = [employee, supervisor_1, department_1
                supervisor_2, department_2, department_3
                company, objectclass]

The above-explained MRO algorithm is also known as C3 is implemented by python and we have a direct formula that returns the MRO of a class. The formula is given by class_name.mro()

class company:
  pass
class department_1(company):
  pass
class department_2(company):
  pass
class department_3(company):
  pass
class supervisor_1(department_1, department_2):
  pass
class supervisor_2(department_2, department_3):
  pass
class employee(supervisor_1, supervisor_2, department_3):
  pass
print(employee.mro())

# Output
[<class '__main__.employee'>, <class '__main__.supervisor_1'>,
<class '__main__.department_1'>, <class '__main__.supervisor_2'>,
<class '__main__.department_2'>, <class '__main__.department_3'>,
<class '__main__.company'>, <class 'object'>]

super() function

super() is an in-built method that is used to access the method, variables, or constructors of the parent class from inside of the child class.

We can also use the super() keyword and call the constructor of the parent class explicitly from the child class itself. This makes the code reusability.

In the below example, we are initializing the instance variable inside the parent class constructor.

If we want to use those values inside the child class we cannot use them because the constructor of the parent class will be executed only if the object of the parent class is created.

So, we have to declare and initialize them again in the child class which makes code look lengthy and memory inefficient.

class company:
    def __init__(self, company_name, company_size, chairman):
        self.company_name = company_name
        self.company_size = company_size
        self.chairman = chairman
    def comp_method(self):
        print(self.company_name, self.company_size, self.chairman)
class employee(company):
    def __init__(self, company_name, company_size, chairman, emp_name, emp_age):
        self.company_name = company_name
        self.company_size = company_size
        self.chairman = chairman
        self.emp_name = emp_name
        self.emp_age = emp_age
    def emp_method(self):
        print(self.company_name, self.company_size, self.chairman)
        print(self.emp_name, self.emp_age)
obj = employee("pysoft", 40, "john", "smith", "35")
obj.emp_method()

The above code is not efficient because we are unable to re-use the constructor of the parent class. But there is no other way because without object creation of the parent class we cannot use the constructor of the parent class.

So this task becomes huge when we need to repeat it for other employees. Instead, we can use the super() method to call the constructor of the parent class and access it from the child class.

class company:
    def __init__(self, company_name):
        self.company_name = company_name
    def comp_method(self):
        print(self.company_name)
class employee(company):
    def __init__(self, company_name, emp_name, emp_age):
        super().__init__(company_name)
        self.emp_name = emp_name
        self.emp_age = emp_age
    def emp_method(self):
        super().comp_method()
        print(self.emp_name, self.emp_age)
obj = employee("pysoft", "smith", "35")
obj.emp_method()

# Output
pysoft 40 john
smith 35

Basically, all the variables, methods, and constructors in the parent class are by default available to the child class and we don’t have to re-define them in the child class.

But if the method in child class and parent class have the same name and if we try to call the method in parent class, by default due to Method Resolution Order the method in the child class is given more priority and is returned.

class parent:
    def m1(self):
        print("parent method")
class child(parent):
    def m1(self):
        print("child method")
obj = child()
obj.m1()

# Output
child method

Thus in such cases to access the parent method we use the super() method which can be used to explicitly call the method in the parent class.

class parent:
    def m1(self):
        print("parent method")
class child(parent):
    def m1(self):
        super().m1()
        print("child method")
obj = child()
obj.m1()

# Output
parent method
child method

In the case of multi-level inheritance, the child class can access the members of all the parent classes available. But if all the parent classes have the same method name with different functionality then due to MRO the depth-most class method will be executed.

class A:
    def meth(self):
        print("Class A method")
        print("sum", 6 + 5)
class B(A):
    def meth(self):
        print("Class B method")
        print("sub", 6 - 5) 
class C(B):
    def meth(self):
        print("Class C method")
        print("mul", 6 * 5) 
class D(C):
    def meth(self):
        print("Class D method")
        print("div", 6 / 5) 
class E(D):
    def child_method(self):
        super().meth()
obj = E()
obj.child_method()

# Output
Class D method
div 1.2

If we want to execute the method from a particular class then we need to pass the class name that we need to access into the super() method. The syntax is given below:-

# Syntax

super(class_name, reference_variable).method_name()
class A:
    def meth(self):
        print("Class A method")
        print("sum", 6 + 5)
class B(A):
    def meth(self):
        print("Class B method")
        print("sub", 6 - 5) 
class C(B):
    def meth(self):
        print("Class C method")
        print("mul", 6 * 5) 
class D(C):
    def meth(self):
        print("Class D method")
        print("div", 6 / 5) 
class E(D):
    def child_method(self):
        super(C, self).meth()
obj = E()
obj.child_method()

# Output
Class B method
sub 1