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.
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 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 the base class __init__() method, rather it can access any of the base class properties be it a method or an attribute.