Inheritance in Python

When you are working with classes and objects, you might come across a situation where you need to extend the functionality of an existing class. Well, to do that you can simply update the existing class. But, there is a possibility that you will make it more complicated or break some functionality that used to work.

Probably, to avoid that you could write a new class. But that will result in maintaining more code and larger code files. Which is also not a good programming approach.

The answer to all these problems is Inheritance.


What is Inheritance?

One of the most important features of object-oriented programming is re-usability. Inheritance is one of those concepts which helps us to reuse our existing code.

Inheritance is the process of deriving a new class from an existing class. This derived class can inherit some or all features from the existing class. Not only it can inherit but also override the behaviors of the old class and implement them as per the new requirement.

When we inherit a class from an existing one, it is known as a child, subclass, or derived class. The existing class that we are inheriting from is known as parent, superclass, or base class.

Inheritance in Python

For instance, cars, buses, trucks, and bikes all are vehicles. They all have some characteristics of vehicles. Yet each of them has some additional features that make them different from others.

So, here, we can think of the Vehicle as a base class and cars, buses, trucks, and bikes as its child or derived classes as they inherit some of the vehicle features.

Python Inheritance Example

Python Inheritance Syntax

To inherit the features of a base class into a child class in Python, we pass the name of the base class inside of the parentheses of the child class. Below is the basic syntax:

class Base:
    body of the base class

class Child(Base):
    body of the child class

Python Inheritance Example

Now that we have a basic idea of what inheritance is, let us now understand it with the help of an example.

To do that let’s first define a base class named Vehicle. A class is called a base class if it is not derived from any other class.

This Vehicle class has a method named vehicalInfo() which prints the information of the vehicle.

# base class
class Vehicle:
    def vehicleInfo(self):
        print('I am a vehicle!')

Let’s now create a subclass named Car from this Vehicle class. To make the Car class subclass of the class Vehicle, we have to pass the Vehicle class inside of the parentheses of the Car class.

# base class
class Vehicle:
    def vehicleInfo(self):
        print('I am a vehicle!')
        
# subclass
class Car(Vehicle):
    pass

The Car class that we have defined has no methods or attributes. But, it is inherited from the Vehicle class. This means all of its objects must be able to use the methods and attributes defined inside the Vehicle class.

So, let’s create an object of the Car class and check if it can use Vehicle class method or not.

# base class
class Vehicle:
    def vehicleInfo(self):
        print('I am a vehicle!')
        
# subclass
class Car(Vehicle):
    pass

# create an object of each class
vehicle = Vehicle()
car = Car()

# call base class method for each object
vehicle.vehicleInfo()
car.vehicleInfo()

Output:

I am a vehicle!
I am a vehicle!

As you can see from the above output, we did not define any vehicalInfo() method inside the Car class. Still, its object is able to call the vehicalInfo() method. This is because it inherits all of the Vehicle class methods and attributes.


Method Overriding in Python

Method overriding is a concept of object-oriented programming that allows us to change the implementation of a method inside the child class that is already defined inside the base class.

This simply means that the base and the child class both have a method with exactly the same name but each performs a different task.

In the previous example, the Car class inherits everything from its base Vehicle class. But, if the child class is the same as the base class, there is no point in creating it. It should implement some of the base class functionality in its way.

So, let’s redefine the vehicleInfo() method inside of the Car class:

# base class
class Vehicle:
    def vehicleInfo(self):
        print('I am a vehicle!')
        
# subclass
class Car(Vehicle):
    def vehicleInfo(self):
        print('I am a car!')

# create an object of each class
vehicle = Vehicle()
car = Car()

# call the vehicleInfo() method
vehicle.vehicleInfo()
car.vehicleInfo()

Output:

I am a vehicle!
I am a car!

Now you can see the difference. Earlier, when we called the vehicleInfo() method using the car object, it printed ‘I am a vehicle’ but now it prints ‘I am a car’.

This is because the Car class vehicleInfo() method overrides the vehicleInfo() method that is defined inside the Vehicle class. So when we call the vehicleInfo() method using the Car class object, only its version of that method is called.

When a method with the same name is defined in a child and base class both, it is known as method overriding.


Extending the Functionality of the Child Class

Base class methods are not the only ones that a child class can have. It can also have new methods which do not exist in the base class.

So, let’s define a new method printSpeed() inside the Car class that prints the car speed.

# base class
class Vehicle:
    def vehicleInfo(self):
        print('I am a vehicle!')
        
# subclass
class Car(Vehicle):
    def vehicleInfo(self):
        print('I am a car!')
        
    # new method    
    def printSpeed(self, speed):
        print('Car is running at a speed of ', speed, 'kms per hour.')

# create an object of each class
vehicle = Vehicle()
car = Car()

# print the speed of the car
car.printSpeed(60)

Output:

Car is running at a speed of  60 kms per hour.

It is to be noted that only child class objects have access to the base class methods and attributes. But, the reverse is not true.

So in the above example, if try to call printSpeed() method using a Vehicle class object, you will get an AttributeError.

# call a child class method using base class object
vehicle.printSpeed(50)

Output:

Traceback (most recent call last):
  File "<string>", line 22, in <module>
AttributeError: 'Vehicle' object has no attribute 'printSpeed'

Only the child class objects can access base class attributes and methods. The reverse is not true.


Python super() Function

In the last few examples, we learned the concept of method overriding where we redefined base class methods inside the child class.

When you are overriding a method, you might have a situation where you need to reuse some functionality of the base class as well as add some new features to this method inside the child class.

This can be very easily achieved using the super() function. Using the super() function we can access the properties and behaviors of the base class.

To demonstrate this, let’s redefine our existing Vehicle and Car class. The new Vehicle class has an attribute named ‘color’ which specifies the color of the vehicle and the Car class has an additional ‘name’ attribute which represents the name of the car.

# base class
class Vehicle:
    def __init__(self, color):
        self.color = color
        
    def vehicleInfo(self):
        print('I am a', self.color, 'color vehicle')
        
# subclass
class Car(Vehicle):
    
    def __init__(self, color, name):
        super().__init__(color)    # call base class __init__() method
        self.name = name
        
    def vehicleInfo(self):
        print('I am a', self.color, 'color', self.name)
        

# create an object of each class
vehicle = Vehicle('black')
car = Car('black', 'Mercedes Benz')

# Print vehicle info
vehicle.vehicleInfo()
car.vehicleInfo()

Output:

I am a black color vehicle
I am a black color Mercedes Benz

Here, the super() function invokes the base class __init__() method. As the base class __init__() method needs a parameter ‘color’, so we have to pass it when we are calling it inside our child class. That is exactly what the statement super().__init__(color) does.

The base class __init__() method then sets the value of the color attribute. Which we have later accessed using the child class object car inside the vehicleInfo() method.

This is what our Car class would have looked like if we hadn’t used the super() function.

class Car(Vehicle):
    
    def __init__(self, color, name):
        self.color = color
        self.name = name
        
    def vehicleInfo(self):
        print('I am a', self.color, 'color', self.name)
        

But that’s not a good programming approach as we are not reusing our code.

The super() function can not only call base class __init__() method, rather it can access any of the of the base class properties be it a method or an attribute.