Python Inheritance#

The process of inheriting the properties of the base (or parent) class into a derived (or child) class is called inheritance. Inheritance enables us to define a class that takes all the functionality from a base class and allows us to add more. In this tutorial, you will learn to use inheritance in Python.

Inheritance in Python#

Inheritance is a powerful feature in object oriented programming. The main purpose of inheritance is the reusability of code because we can use the existing class to create a new class instead of creating it from scratch.

It refers to defining a new class with little or no modification to an existing class. The new class is called derived (or child) class and the one from which it inherits is called the base (or parent) class.

In inheritance, the derived class acquires and access all the data members, properties, and functions from the base class. Also, a derived class can also provide its specific implementation to the methods of the base class.

Syntax:

class BaseClass:
    Body of base class
class DerivedClass(BaseClass):
    Body of derived class

Types Of Inheritance#

In Python, based upon the number of child and parent classes involved, there are five types of inheritance. The type of inheritance are listed below:

  1. Single Inheritance

  2. Multiple Inheritance

  3. Multilevel inheritance

  4. Hierarchical Inheritance

  5. Hybrid Inheritance Now let’s see each in detail with example.

Python Single Inheritance#

In single inheritance, a derived class inherits from a single-base class. Here in one derived class and one base class.

Here, the Derived class is derived from Base class.

# Example 1: Single Inheritance

# Base class
class Vehicle:
    def Vehicle_info(self):
        print('Inside Vehicle class')

# Derived class
class Car(Vehicle):
    def car_info(self):
        print('Inside Car class')

# Create object of Car
car = Car()

# access Vehicle's info using car object
car.Vehicle_info()
car.car_info()
Inside Vehicle class
Inside Car class

Python Multiple Inheritance#

In multiple inheritance, one derived class can inherit from multiple base classes.

Here, the MultiDerived class is derived from Base1 and Base2 classes.

# Example 1: Multiple Inheritance

# Base class 1
class Person:
    def person_info(self, name, age):
        print('Inside Person class')
        print('Name:', name, 'Age:', age)

# Base class 2
class Company:
    def company_info(self, company_name, location):
        print('Inside Company class')
        print('Name:', company_name, 'location:', location)

# Derived class
class Employee(Person, Company):
    def Employee_info(self, salary, skill):
        print('Inside Employee class')
        print('Salary:', salary, 'Skill:', skill)

# Create object of Employee
emp = Employee()

# access data
emp.person_info('Milaan', 33)
emp.company_info('Google', 'Atlanta')
emp.Employee_info(19000, 'Machine Learning')
Inside Person class
Name: Milaan Age: 33
Inside Company class
Name: Google location: Atlanta
Inside Employee class
Salary: 19000 Skill: Machine Learning
# Example 2:

class First(object):  # Base class1
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):  # Base class2
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(Second, First):  # Derived class derived from Base class 1 and Base class 2
    def __init__(self):
        super(Third, self).__init__()
        print("third")

Third(); #call Third class constructor
first
second
third
# Example 3:

class Mammal(object):
    def __init__(self, mammalName):
        print(mammalName, 'is a warm-blooded animal.')
    
class Dog(Mammal):
    def __init__(self):
        print('Dog has four legs.')
        super().__init__('Dog')
    
d1 = Dog()
Dog has four legs.
Dog is a warm-blooded animal.

Explanation:

Here, we called the __init__() method of the Mammal class (from the Dog class) using code

super().__init__('Dog')

instead of

Mammal.__init__(self, 'Dog')

Since we do not need to specify the name of the base class when we call its members, we can easily change the base class name (if we need to).

# changing base class to CanidaeFamily
class Dog(CanidaeFamily):
  def __init__(self):
    print('Dog has four legs.')

    # no need to change this
    super().__init__('Dog')

The super() built-in function returns a proxy object, a substitute object that can call methods of the base class via delegation. This is called indirection (ability to reference base object with super() built-in function).

Since the indirection is computed at the runtime, we can use different base classes at different times (if we need to).

# Example 4:

class Animal:
    def __init__(self, Animal):
        print(Animal, 'is an animal.');

class Mammal(Animal):  # Mammal derived to Animal
    def __init__(self, mammalName):
        print(mammalName, 'is a warm-blooded animal.')
        super().__init__(mammalName)
    
class NonWingedMammal(Mammal):  # NonWingedMammal derived from Mammal (derived from Animal)
    def __init__(self, NonWingedMammal):
        print(NonWingedMammal, "can't fly.")
        super().__init__(NonWingedMammal)

class NonMarineMammal(Mammal):  # NonMarineMammal derived from Mammal (derived from Animal)
    def __init__(self, NonMarineMammal):
        print(NonMarineMammal, "can't swim.")
        super().__init__(NonMarineMammal)

class Dog(NonMarineMammal, NonWingedMammal):  # Dog derived from NonMarineMammal and NonWingedMammal
    def __init__(self):
        print('Dog has 4 legs.');
        super().__init__('Dog')
    
d = Dog()
print('')
bat = NonMarineMammal('Bat')
Dog has 4 legs.
Dog can't swim.
Dog can't fly.
Dog is a warm-blooded animal.
Dog is an animal.

Bat can't swim.
Bat is a warm-blooded animal.
Bat is an animal.

Why super() keyword#

The super() function is most commonly used with __init__ function in base class. This is usually the only place where we need to do some things in a child then complete the initialization in the parent.

class Child(Parent):
    def __init__(self, stuff)
        self.stuff = stuff
        super(Child, self).__init__()

Private members of parent class#

We don’t always want the instance variables of the parent class to be inherited by the child class i.e. we can make some of the instance variables of the parent class private, which won’t be available to the child class. We can make an instance variable by adding double underscores before its name.

## Example:

# Python program to demonstrate private members
# of the parent class
class C(object):
    def __init__(self):
            self.c = 21
            # d is private instance variable
            self.__d = 42  # Note: before 'd' there are two '_'

class D(C):
    def __init__(self):
            self.e = 84
            C.__init__(self)

object1 = D()
# produces an error as d is private instance variable
print(D.d)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[1], line 18
     16 object1 = D()
     17 # produces an error as d is private instance variable
---> 18 print(D.d)

AttributeError: type object 'D' has no attribute 'd'

Python Multilevel Inheritance#

In multilevel inheritance, we can also inherit from a derived class. It can be of any depth in Python.

All the features of the base class and the derived class are inherited into the new derived class.

For example, there are three classes A, B, C. A is the superclass, B is the child class of A, C is the child class of B. In other words, we can say a chain of classes is called multilevel inheritance.

Here, the Derived1 class is derived from the Base class, and the Derived2 class is derived from the Derived1 class.

# Example 1:

# Base class
class Vehicle:
    def Vehicle_info(self):
        print('Inside Vehicle class')

# Child class
class Car(Vehicle):
    def car_info(self):
        print('Inside Car class')

# Child class
class SportsCar(Car):
    def sports_car_info(self):
        print('Inside SportsCar class')

# Create object of SportsCar
s_car = SportsCar()

# access Vehicle's and Car info using SportsCar object
s_car.Vehicle_info()
s_car.car_info()
s_car.sports_car_info()
Inside Vehicle class
Inside Car class
Inside SportsCar class
# Example 2:

class Animal:  # grandparent class
    def eat(self):
        print('Eating...')

class Dog(Animal):  # parent class
    def bark(self):
        print('Barking...')

class BabyDog(Dog):  # child class
    def weep(self):
        print('Weeping...')

d=BabyDog()
d.eat()
d.bark()
d.weep()
Eating...
Barking...
Weeping...

Hierarchical Inheritance#

In Hierarchical inheritance, more than one child class is derived from a single parent class. In other words, we can say one base class and multiple derived classes.

# Example 1:

class Vehicle:
    def info(self):
        print("This is Vehicle")

class Car(Vehicle):
    def car_info(self, name):
        print("Car name is:", name)

class Truck(Vehicle):
    def truck_info(self, name):
        print("Truck name is:", name)

obj1 = Car()
obj1.info()
obj1.car_info('BMW')

obj2 = Truck()
obj2.info()
obj2.truck_info('Ford')
This is Vehicle
Car name is: BMW
This is Vehicle
Truck name is: Ford

Hybrid Inheritance#

When inheritance is consists of multiple types or a combination of different inheritance is called hybrid inheritance.

# Example 1:

class Vehicle:
    def vehicle_info(self):
        print("Inside Vehicle class")

class Car(Vehicle):
    def car_info(self):
        print("Inside Car class")

class Truck(Vehicle):
    def truck_info(self):
        print("Inside Truck class")

# Sports Car can inherits properties of Vehicle and Car
class SportsCar(Car, Vehicle):
    def sports_car_info(self):
        print("Inside SportsCar class")

# create object
s_car = SportsCar()

s_car.vehicle_info()
s_car.car_info()
s_car.sports_car_info()
Inside Vehicle class
Inside Car class
Inside SportsCar class

Example of Inheritance in Python#

To demonstrate the use of inheritance, let us take an example.

A polygon is a closed figure with 3 or more sides. Say, we have a class called Polygon defined as follows.

class Polygon:
    def __init__(self, no_of_sides):
        self.n = no_of_sides
        self.sides = [0 for i in range(no_of_sides)]

    def inputSides(self):
        self.sides = [float(input("Enter side "+str(i+1)+" : ")) for i in range(self.n)]

    def dispSides(self):
        for i in range(self.n):
            print("Side",i+1,"is",self.sides[i])

This class has data attributes to store the number of sides n and magnitude of each side as a list called sides.

The inputSides() method takes in the magnitude of each side and dispSides() displays these side lengths.

A triangle is a polygon with 3 sides. So, we can create a class called Triangle which inherits from Polygon. This makes all the attributes of Polygon class available to the Triangle class.

We don’t need to define them again (code reusability). Triangle can be defined as follows.

class Triangle(Polygon):
    def __init__(self):
        Polygon.__init__(self,3)

    def findArea(self):
        a, b, c = self.sides
        # calculate the semi-perimeter
        s = (a + b + c) / 2
        area = (s*(s-a)*(s-b)*(s-c)) ** 0.5
        print('The area of the triangle is %0.2f' %area)

However, class Triangle has a new method findArea() to find and print the area of the triangle. Here is a sample run.

t = Triangle()
t.inputSides()
Enter side 1 : 5
Enter side 2 : 3
Enter side 3 : 6
t.dispSides()
Side 1 is 5.0
Side 2 is 3.0
Side 3 is 6.0
t.findArea()
The area of the triangle is 7.48

We can see that even though we did not define methods like inputSides() or dispSides() for class Triangle separately, we were able to use them.

If an attribute is not found in the class itself, the search continues to the base class. This repeats recursively, if the base class is itself derived from other classes.

# Example 1:

class Parent: # define parent class
    parentAttr = 100

    def __init__(self):
        print ("Calling parent constructor")

    def parentMethod(self):
        print ('Calling parent method')

    def setAttr(self, attr):
        Parent.parentAttr = attr

    def getAttr(self):
        print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
    def __init__(self):
        print ("Calling child constructor")

    def childMethod(self):
        print ('Calling child method')

c = Child() # instance of child
c.childMethod() # child calls its method
c.parentMethod() # calls parent's method
c.setAttr(200) # again call parent's method
c.getAttr() # again call parent's method
Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

Explanation:

In the above example, hierarchical and multiple inheritance exists. Here we created, parent class Vehicle and two child classes named Car and Truck this is hierarchical inheritance.

Another is SportsCar inherit from two parent classes named Car and Vehicle. This is multiple inheritance.

Python super() function#

When a class inherits all properties and behavior from the parent class is called inheritance. In such a case, the inherited class is a subclass and the latter class is the parent class.

In child class, we can refer to parent class by using the super() function. Python super() function returns a temporary object of the parent class that allows us to call a parent class method inside a child class method.

Benefits of using the super() function are:

  • We are not required to remember or specify the parent class name to access its methods.

  • We can use the super() function in both single and multiple inheritances.

  • The super() function support code reusability as there is no need to write the entire function

# Example 1:

class Company:
    def company_name(self):
        return 'Google'

class Employee(Company):
    def info(self):
        # Calling the superclass method using super()function
        c_name = super().company_name()
        print("Arthur works at", c_name)

# Creating object of child class
emp = Employee()
emp.info()
Arthur works at Google

Explanation:

In the above example, we create a parent class Company and child class Employee. In Employee class, we call the parent class method by using a super() function.

issubclass()#

In Python, we can verify whether a particular class is a subclass of another class. For this purpose, we can use Python issubclass() function. This function returns True if the given class is the subclass of the specified class. Otherwise, it returns False.

Syntax:

issubclass(class, classinfo)

Where,

  • class: class to be checked.

  • classinfo: a class, type, or a tuple of classes or data types.

# Example 1:

class Company:
    def fun1(self):
        print("Inside parent class")

class Employee(Company):
    def fun2(self):
        print("Inside child class.")

class Player:
    def fun3(self):
        print("Inside Player class.")

# Result True
print(issubclass(Employee, Company))

# Result False
print(issubclass(Employee, list))

# Result False
print(issubclass(Player, Company))

# Result True
print(issubclass(Employee, (list, Company)))

# Result True
print(issubclass(Company, (list, Company)))
True
False
False
True
True

Method Overriding#

In inheritance, all members available in the parent class are by default available in the child class. If the child class does not satisfy with parent class implementation, then the child class is allowed to redefine that method by extending additional functions in the child class. This concept is called method overriding.

When a child class method has the same name, same parameters, and same return type as a method in its superclass, then the method in the child is said to override the method in the parent class.

# Example 1:

class Vehicle:
    def max_speed(self):
        print("max speed is 100 Km/Hour")

class Car(Vehicle):
    # overridden the implementation of Vehicle class
    def max_speed(self):
        print("max speed is 200 Km/Hour")

# Creating object of Car class
car = Car()
car.max_speed()
max speed is 200 Km/Hour

Explanation:

In the above example, we create two classes named Vehicle (Parent class) and Car (Child class). The class Car extends from the class Vehicle so, all properties of the parent class are available in the child class. In addition to that, the child class redefined the method max_speed().

# Example 2:

class Parent: # define parent class
    def myMethod(self):
        print ('Calling parent method')


class Child(Parent): # define child class
    def myMethod(self):
        print ('Calling child method')


c = Child() # instance of child
c.myMethod() # child calls overridden method
Calling child method
# Example 3:

# parent class
class Bird:
    
    def __init__(self):
        print("Bird is ready")

    def whoisThis(self):
        print("Bird")

    def swim(self):
        print("Swim faster")

# child class
class Penguin(Bird):

    def __init__(self):
        # call super() function to run the __init__() method of the parent class inside the child class.
        super().__init__()
        print("Penguin is ready")

    def whoisThis(self):
        print("Penguin")

    def run(self):
        print("Run faster")

peggy = Penguin()
peggy.whoisThis()
peggy.swim()
peggy.run()

#issubclass(Penguin, Bird) 
isinstance(peggy, Bird)
Bird is ready
Penguin is ready
Penguin
Swim faster
Run faster
True

Example of Method Overriding in Python#

In the example of “Polygon” and “Triangle”, notice that __init__() method was defined in both classes, Triangle as well Polygon. When this happens, the method in the derived class overrides that in the base class. This is to say, __init__() in Triangle gets preference over the __init__ in Polygon.

Generally when overriding a base method, we tend to extend the definition rather than simply replace it. The same is being done by calling the method in base class from the one in derived class (calling Polygon.__init__() from __init__() in Triangle).

A better option would be to use the built-in function super(). So, super().__init__(3) is equivalent to Polygon.__init__(self,3) and is preferred. To learn more about the super() function in Python, visit Python super() function.

Two built-in functions isinstance() and issubclass() are used to check inheritances.

The function isinstance() returns True if the object is an instance of the class or other classes derived from it. Each and every class in Python inherits from the base class object.

isinstance(t,Triangle)
True
isinstance(t,Polygon)
True
isinstance(t,int)
False
isinstance(t,object)
True

Similarly, issubclass() is used to check for class inheritance.

issubclass(Polygon,Triangle)
False
issubclass(Triangle,Polygon)
True
issubclass(bool,int)
True

Method Resolution Order in Python#

In Python, Method Resolution Order(MRO) is the order by which Python looks for a method or attribute.

First, the method or attribute is searched within a class, and then it follows the order we specified while inheriting.

This order is also called the Linearization of a class, and a set of rules is called MRO (Method Resolution Order). The MRO plays an essential role in multiple inheritances as a single method may found in multiple parent classes.

In multiple inheritance, the following search order is followed.

  1. First, it searches in the current parent class if not available, then searches in the parents class specified while inheriting (that is left to right.)

  2. We can get the MRO of a class. For this purpose, we can use either the mro attribute or the mro() method.

Example:

class A:
    def process(self):
        print(" In class A")

class B(A):
    def process(self):
        print(" In class B")

class C(B, A):
    def process(self):
        print(" In class C")

# Creating object of C class
C1 = C()
C1.process()
print(C.mro())
# In class C
# [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

Explanation:

In the above example, we create three classes named A, B and C. Class B is inherited from A, class C inherits from B and A. When we create an object of the C class and calling the process() method, Python looks for the process() method in the current class in the C class itself.

Then search for parent classes, namely B and A, because C class inherit from B and A. that is, C(B, A) and always search in left to right manner.

# Output: True
print(issubclass(list,object))
True
# Output: True
print(isinstance(5.5,object))
True
# Output: True
print(isinstance("Hello",object))
True

In the multiple inheritance scenario, any specified attribute is searched first in the current class. If not found, the search continues into parent classes in depth-first, left-right fashion without searching the same class twice.

So, in the above example of MultiDerived class the search order is [MultiDerived, Base1, Base2, object]. This order is also called linearization of MultiDerived class and the set of rules used to find this order is called Method Resolution Order (MRO).

MRO must prevent local precedence ordering and also provide monotonicity. It ensures that a class always appears before its parents. In case of multiple parents, the order is the same as tuples of base classes.

MRO of a class can be viewed as the __mro__ attribute or the mro() method. The former returns a tuple while the latter returns a list

>>> MultiDerived.__mro__
(<class '__main__.MultiDerived'>,
 <class '__main__.Base1'>,
 <class '__main__.Base2'>,
 <class 'object'>)

>>> MultiDerived.mro()
[<class '__main__.MultiDerived'>,
 <class '__main__.Base1'>,
 <class '__main__.Base2'>,
 <class 'object'>]

Here is a little more complex multiple inheritance example and its visualization along with the MRO.

# Demonstration of MRO

class X:
    pass


class Y:
    pass


class Z:
    pass


class A(X, Y):
    pass


class B(Y, Z):
    pass


class M(B, A, Z):
    pass

# Output:
# [<class '__main__.M'>, <class '__main__.B'>,
#  <class '__main__.A'>, <class '__main__.X'>,
#  <class '__main__.Y'>, <class '__main__.Z'>,
#  <class 'object'>]

print(M.mro())
[<class '__main__.M'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class 'object'>]

To know the actual algorithm on how MRO is calculated, visit Discussion on MRO.