10. Inheritance
By Bernd Klein. Last modified: 24 Mar 2024.
Introduction and Definitions
No object-oriented programming language would be worthy to look at or use, if it didn't support inheritance. Inheritance was invented in 1969 for Simula. Python not only supports inheritance but multiple inheritance as well. Generally speaking, inheritance is the mechanism of deriving new classes from existing ones. By doing this, we get a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance (a "child object") acquires all, - though there are exceptions in some programming languages, - of the properties and behaviors of the parent object.
Inheritance allows programmers to create classes that are built upon existing classes, and this enables a class created through inheritance to inherit the attributes and methods of the parent class. This means that inheritance supports code reusability. The methods or generally speaking the software inherited by a subclass is considered to be reused in the subclass. The relationships of objects or classes through inheritance give rise to a directed graph.
The class from which a class inherits is called the parent or superclass. A class which inherits from a superclass is called a subclass, also called heir class or child class. Superclasses are sometimes called ancestors as well. There exists a hierarchical relationship between classes. It's similar to relationships or categorizations that we know from real life. Think about vehicles, for example. Bikes, cars, buses and trucks are vehicles. Pick-ups, vans, sports cars, convertibles and estate cars are all cars and by being cars they are vehicles as well. We could implement a vehicle class in Python, which might have methods like accelerate and brake. Cars, Buses and Trucks and Bikes can be implemented as subclasses which will inherit these methods from vehicle.
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Syntax of Inheritance in Python
The syntax for a subclass definition looks like this:
class DerivedClassName(BaseClassName):
pass
Instead of the pass
statement, there will be methods and attributes like in all other classes. The name BaseClassName must be defined in a scope containing the derived class definition.
Now we are ready for a simple inheritance example with Python code.
Simple Inheritance Example
We will stick with our beloved robots or better Robot
class from the previous chapters of our Python tutorial to show how the principle of inheritance works. We will define a class PhysicianRobot
, which inherits from Robot
.
class Robot:
def __init__(self, name):
self.name = name
def say_hi(self):
print("Hi, I am " + self.name)
class PhysicianRobot(Robot):
pass
x = Robot("Marvin")
y = PhysicianRobot("James")
print(x, type(x))
print(y, type(y))
y.say_hi()
OUTPUT:
<__main__.Robot object at 0x7f5c9e46fb10> <class '__main__.Robot'> <__main__.PhysicianRobot object at 0x7f5c9e48b010> <class '__main__.PhysicianRobot'> Hi, I am James
If you look at the code of our PhysicianRobot
class, you can see that we haven't defined any attributes or methods in this class. As the class PhysicianRobot
is a subclass of Robot
, it inherits, in this case, both the method __init__
and say_hi
. Inheriting these methods means that we can use them as if they were defined in the PhysicianRobot
class. When we create an instance of PhysicianRobot
, the __init__
function will also create a name attribute. We can apply the say_hi
method to the PhysisicianRobot
object y
, as we can see in the output from the code above.
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Upcoming online Courses
Difference between type
and isinstance
You should also pay attention to the following facts, which we pointed out in other sections of our Python tutorial as well. People frequently ask where the difference between checking the type via the type
function or the function isinstance
is lies.
The difference can be seen in the following code. We see that isinstance
returns True
if we compare an object either with the class it belongs to or with the superclass. Whereas the equality operator only returns True
, if we compare an object with its own class.
x = Robot("Marvin")
y = PhysicianRobot("James")
print(isinstance(x, Robot), isinstance(y, Robot))
print(isinstance(x, PhysicianRobot))
print(isinstance(y, PhysicianRobot))
print(type(y) == Robot, type(y) == PhysicianRobot)
OUTPUT:
True True False True False True
This is even true for arbitrary ancestors of the class in the inheritance line:
class A:
pass
class B(A):
pass
class C(B):
pass
x = C()
print(isinstance(x, A))
OUTPUT:
True
Now it should be clear, why PEP 8, the official Style Guide for Python code, says: "Object type comparisons should always use isinstance() instead of comparing types directly."
Overriding
Let us get back to our new PhysicianRobot
class.
Imagine now that an instance of a PhysicianRobot
should say hi in a different way. In this case, we have to redefine the method say_hi
inside of the subclass PhysicianRobot
:
class Robot:
def __init__(self, name):
self.name = name
def say_hi(self):
print("Hi, I am " + self.name)
class PhysicianRobot(Robot):
def say_hi(self):
print("Everything will be okay! ")
print(self.name + " takes care of you!")
y = PhysicianRobot("James")
y.say_hi()
OUTPUT:
Everything will be okay! James takes care of you!
What we have done in the previous example is called overriding. A method of a parent class gets overridden by simply defining a method with the same name in the child class.
If a method is overridden in a class, the original method can still be accessed, but we have to do it by calling the method directly with the class name, i.e. Robot.say_hi(y)
. We demonstrate this in the following code:
y = PhysicianRobot("Doc James")
y.say_hi()
print("... and now the 'traditional' robot way of saying hi :-)")
Robot.say_hi(y)
OUTPUT:
Everything will be okay! Doc James takes care of you! ... and now the 'traditional' robot way of saying hi :-) Hi, I am Doc James
We have seen that an inherited class can inherit and override methods from the superclass. Besides this a subclass often needs additional methods with additional functionalities, which do not exist in the superclass. An instance of the PhysicianRobot
class will need for example the method heal
so that the physician can do a proper job.
We will also add an attribute health_level
to the Robot
class, which can take a value between 0
and 1
. The robots will 'come to live' with a random value between 0
and 1
. If the health_level
of a Robot
is below 0.8, it will need a doctor. We write a method needs_a_doctor
which returns True
if the value is below 0.8
and False
otherwise. The 'healing' in the heal
method is done by setting the health_level
to a random value between the old health_level
and 1. This value is calculated by the uniform function of the random module.
import random
class Robot:
def __init__(self, name):
self.name = name
self.health_level = random.random()
def say_hi(self):
print("Hi, I am " + self.name)
def needs_a_doctor(self):
if self.health_level < 0.8:
return True
else:
return False
class PhysicianRobot(Robot):
def say_hi(self):
print("Everything will be okay! ")
print(self.name + " takes care of you!")
def heal(self, robo):
robo.health_level = random.uniform(robo.health_level, 1)
print(robo.name + " has been healed by " + self.name + "!")
doc = PhysicianRobot("Dr. Frankenstein")
rob_list = []
for i in range(5):
x = Robot("Marvin" + str(i))
if x.needs_a_doctor():
print("health_level of " + x.name + " before healing: ", x.health_level)
doc.heal(x)
print("health_level of " + x.name + " after healing: ", x.health_level)
rob_list.append((x.name, x.health_level))
print(rob_list)
OUTPUT:
health_level of Marvin0 before healing: 0.27804255004922096 Marvin0 has been healed by Dr. Frankenstein! health_level of Marvin0 after healing: 0.5975612946834138 health_level of Marvin1 before healing: 0.3268433497141947 Marvin1 has been healed by Dr. Frankenstein! health_level of Marvin1 after healing: 0.4658504587635991 health_level of Marvin2 before healing: 0.3070114314391247 Marvin2 has been healed by Dr. Frankenstein! health_level of Marvin2 after healing: 0.5060809357227096 health_level of Marvin3 before healing: 0.15192097831635076 Marvin3 has been healed by Dr. Frankenstein! health_level of Marvin3 after healing: 0.8033835535943196 health_level of Marvin4 before healing: 0.16006553707176496 Marvin4 has been healed by Dr. Frankenstein! health_level of Marvin4 after healing: 0.6370537193011726 [('Marvin0', 0.5975612946834138), ('Marvin1', 0.4658504587635991), ('Marvin2', 0.5060809357227096), ('Marvin3', 0.8033835535943196), ('Marvin4', 0.6370537193011726)]
When we override a method, we sometimes want to reuse the method of the parent class and at some new stuff. To demonstrate this, we will write a new version of the PhysicianRobot. say_hi
should return the text from the Robot class version plus the text " and I am a physician!"
class PhysicianRobot(Robot):
def say_hi(self):
Robot.say_hi(self)
print("and I am a physician!")
doc = PhysicianRobot("Dr. Frankenstein")
doc.say_hi()
OUTPUT:
Hi, I am Dr. Frankenstein and I am a physician!
We don't want to write redundant code and therefore we called Robot.say_hi(self)
. We could also use the super
function:
class PhysicianRobot(Robot):
def say_hi(self):
super().say_hi()
print("and I am a physician!")
doc = PhysicianRobot("Dr. Frankenstein")
doc.say_hi()
OUTPUT:
Hi, I am Dr. Frankenstein and I am a physician!
super
is not realls necessary in this case. One could argue that it makes the code more maintainable, because we could change the name of the parent class, but this is seldom done anyway in existing classes. The real benefit of super
shows when we use it with multiple inheritance.
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Distinction between Overwriting, Overloading and Overriding
Overwriting
If we overwrite a function, the original function will be gone. The function will be redefined. This process has nothing to do with object orientation or inheritance.
def f(x):
return x + 42
print(f(3))
# f will be overwritten (or redefined) in the following:
def f(x):
return x + 43
print(f(3))
OUTPUT:
45 46
Overloading
This subchapter will be only interesting for C++ and Java programmers who want to know how overloading can be accomplished in Python. Those who do not know about overloading will not miss it!
In the context of object-oriented programming, you might have heard about "overloading" as well. Even though "overloading" is not directly connected to OOP. Overloading is the ability to define a function with the same name multiple times. The definitions are different concerning the number of parameters and types of the parameters. It's the ability of one function to perform different tasks, depending on the number of parameters or the types of the parameters. We cannot overload functions like this in Python, but it is not necessary either.
This course is, however, not about C++ and we have so far avoided using any C++ code. We want to make an exception now, so that you can see, how overloading works in C++.
#include#include using namespace std;
int successor(int number) { return number + 1; }
double successor(double number) { return number + 1; }
int main() {
cout << successor(10) << endl; cout << successor(10.3) << endl; return 0;
}
We defined the successor function twice: One time for int and the other time with float as a Parameter. In Python the function can be defined like this, as you will know for sure:
def successor(x):
return x + 1
As x is only a reference to an object, the Python function successor can be called with every object, even though it will create exceptions with many types. But it will work with int and float values!
Having a function with a different number of parameters is another way of function overloading. The following C++ program shows such an example. The function f can be called with either one or two integer arguments:
#includeusing namespace std; int f(int n); int f(int n, int m); int main() { cout << "f(3): " << f(3) << endl; cout << "f(3, 4): " << f(3, 4) << endl; return 0; } int f(int n) { return n + 42; } int f(int n, int m) { return n + m + 42; }
This doesn't work in Python, as we can see in the following example. The second definition of f with two parameters redefines or overrides the first definition with one argument. Overriding means that the first definition is not available anymore.
def f(n):
return n + 42
def f(n,m):
return n + m + 42
print(f(3, 4))
OUTPUT:
49
If you call f with only one parameter, you will raise an exception:
f(3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)in ----> 1 f(3) TypeError: f() missing 1 required positional argument: 'm'
Yet, it is possible to simulate the overloading behaviour of C++ in Python in this case with a default parameter:
def f(n, m=None):
if m:
return n + m +42
else:
return n + 42
print(f(3), f(1, 3))
OUTPUT:
45 46
The * operator can be used as a more general approach for a family of functions with 1, 2, 3, or even more parameters:
def f(*x):
if len(x) == 1:
return x[0] + 42
elif len(x) == 2:
return x[0] - x[1] + 5
else:
return 2 * x[0] + x[1] + 42
print(f(3), f(1, 2), f(3, 2, 1))
OUTPUT:
45 4 50
Overriding
Overriding is already explained above!
Exercises
Exercise 1
Create a class hierarchy for different types of animals, including mammals, birds, and reptiles, using inheritance.
Hints:
- Define a base class called
Animal
with common attributes likename
,age
, andsound
. - Implement subclasses for specific animal types such as
Mammal
,Bird
, andReptile
. Each subclass should inherit from theAnimal
class. - Incorporate additional attributes and methods specific to each animal type. For example, a
Mammal
class might have attributes likefur_color
,number_of_legs
, and methods likegive_birth
andnurse_young
. - Use inheritance to create subclasses representing specific species within each animal type. For example, within the
Mammal
class, create subclasses forDog
,Cat
, andHorse
. - Implement methods or attributes in the subclasses to demonstrate how inheritance allows for the sharing of attributes and methods from parent classes.
- Create instances of the various animal classes and test their functionality to ensure that attributes and methods work as expected.
Exercise 2:
Create a class hierarchy for different types of geometric shapes, including circles, rectangles, and triangles, using inheritance.
Tasks:
- Define a base class called
Shape
with common attributes likecolor
andarea
. - Implement subclasses for specific shape types such as
Circle
,Rectangle
, andTriangle
. Each subclass should inherit from theShape
class. - Incorporate additional attributes and methods specific to each shape type. For example, a
Circle
class might have attributes likeradius
and methods likecalculate_area
. - Use inheritance to create subclasses representing variations within each shape type. For example, within the
Rectangle
class, create subclasses forSquare
andParallelogram
. - Implement methods or attributes in the subclasses to demonstrate how inheritance allows for the sharing of attributes and methods from parent classes.
- Create instances of the various shape classes and test their functionality to ensure that attributes and methods work as expected.
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Solutions to our Exercises
Solution to Exercise 1
class Animal:
def __init__(self, name, age, sound):
"""
Initialize the Animal object with a name, age, and sound.
"""
self.name = name
self.age = age
self.sound = sound
def make_sound(self):
"""
Make the sound associated with the animal.
"""
print(f"{self.name} says: {self.sound}")
class Mammal(Animal):
def __init__(self, name, age, sound, fur_color, number_of_legs):
"""
Initialize the Mammal object with additional attributes specific to mammals.
"""
super().__init__(name, age, sound)
self.fur_color = fur_color
self.number_of_legs = number_of_legs
def give_birth(self, name):
"""
giving birth to a new mamal
"""
return Mammal(name,
age=0,
sound=self.sound,
fur_color=self.fur_color,
number_of_legs=self.number_of_legs)
def nurse_young(self):
"""
Simulate nursing young (common for mammals).
"""
print(f"{self.name} nurses its young.")
class Bird(Animal):
def __init__(self, name, age, sound, wingspan):
"""
Initialize the Bird object with additional attributes specific to birds.
"""
super().__init__(name, age, sound)
self.wingspan = wingspan
def fly(self):
"""
Simulate flying (common for birds).
"""
print(f"{self.name} flies with a wingspan of {self.wingspan}.")
class Reptile(Animal):
def __init__(self, name, age, sound, scale_color):
"""
Initialize the Reptile object with additional attributes specific to reptiles.
"""
super().__init__(name, age, sound)
self.scale_color = scale_color
def crawl(self):
"""
Simulate crawling (common for reptiles).
"""
print(f"{self.name} crawls with {self.scale_color} scales.")
# Create instances of specific animals
dog = Mammal("Molly", 5, "Woof", "Brown", 4)
eagle = Bird("Eagle", 3, "Screech", "Large")
turtle = Reptile("Turtle", 10, "Hiss", "Green")
# Test methods
dog.make_sound()
baby_dog = dog.give_birth('Charlie')
baby_dog.make_sound()
eagle.make_sound()
eagle.fly()
turtle.make_sound()
turtle.crawl()
OUTPUT:
Molly says: Woof Charlie says: Woof Eagle says: Screech Eagle flies with a wingspan of Large. Turtle says: Hiss Turtle crawls with Green scales.
Solution to Exercise 2
import math
class Shape:
def __init__(self, color):
"""
Initialize the Shape object with a color.
"""
self.color = color
def calculate_area(self):
"""
Calculate the area of the shape.
"""
pass # Placeholder method, to be implemented in subclasses
class Circle(Shape):
def __init__(self, color, radius):
"""
Initialize the Circle object with additional attributes specific to circles.
"""
super().__init__(color)
self.radius = radius
def calculate_area(self):
"""
Calculate the area of the circle.
"""
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, color, width, height):
"""
Initialize the Rectangle object with additional attributes specific to rectangles.
"""
super().__init__(color)
self.width = width
self.height = height
def calculate_area(self):
"""
Calculate the area of the rectangle.
"""
return self.width * self.height
class Triangle(Shape):
def __init__(self, color, base, height):
"""
Initialize the Triangle object with additional attributes specific to triangles.
"""
super().__init__(color)
self.base = base
self.height = height
def calculate_area(self):
"""
Calculate the area of the triangle.
"""
return 0.5 * self.base * self.height
# Create instances of specific shapes and test their functionality
circle = Circle("Red", 5)
rectangle = Rectangle("Blue", 4, 6)
triangle = Triangle("Green", 3, 4)
print("Circle Area:", circle.calculate_area()) # Output: Circle Area: 78.54
print("Rectangle Area:", rectangle.calculate_area()) # Output: Rectangle Area: 24
print("Triangle Area:", triangle.calculate_area()) # Output: Triangle Area: 6
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Upcoming online Courses