Inheritance in Python

You have the eyes of your mother. One could say, you “inherited” the eyes of your mother. As you may have guessed, this article is about inheritance in Python.

Inheritance is one of the most important features of object orientation. It’s a simple and intuitive concept but even advanced coders circumvent using inheritance because they have this latent feeling of not understanding it 100%. This harms their ability to write clean and effective code, and–ultimately — harms their ability to operate to their full potential. Do you think you could do more to live up to your potential? Good. Then, let’s start learning inheritance in Python.

As a small gift of appreciation for reading this tutorial and improving your programming skills, you’ll get a free PDF download of my Object Orientation Cheat Sheet at the end of this tutorial.

What is Inheritance in Python?

Inheritance allows you to define a class that inherits all methods and properties from another class.

  • Parent class, also denoted as base class, is the class you inherit from. In Python, every class can be a parent class.
  • Child class, also denoted as derived class, inherits from the Parent class. In Python, you can create a child class that inherits all methods and attributes from the Parent using the class Child(Parent) syntax with the parent class enclosed in parentheses.

Here’s the minimal syntax showing how to derive a child class from a parent class using the highlighted parentheses method:

class Parent:
    pass

class Child(Parent):
    pass

In the following example, you create two classes Parent and Child. Parent has one method p() that prints the string 'from parent' when executed. Child has method c() that prints the string 'from child' and the inherited method p() from the Parent class.

# Define parent and child classes
class Parent:
    def p(self):
        return 'from parent'


# Child inherits method p() from parent
class Child(Parent):
    def c(self):
        return 'from child'


# Create parent instance and run method
parent = Parent()
print(parent.p())

# Create child instance and run methods
child = Child()
print(child.p())
print(child.c())

The output is:

from parent
from parent
from child

You can see that the child inherits all methods and, not shown, all attributes from the parent class. This can save you a lot of redundancies in practice.

Video Overview

Puzzle and Negative Example

Let’s have a look at a bad example NOT using inheritance. Keep your code understanding skills fresh by solving the puzzle first before you read on.

class Human:

    def __init__(self, name, ff, iq):
        self.name = name
        self.ff = ff # = facebook friends
        self.iq = iq

    def befriend(self, other):
        self.ff += 1
        other.ff += 1

    def learn(self):
        self.iq += 1



class Wizard:

    def __init__(self, name, ff, iq, mana):
        self.name = name
        self.ff = ff # = facebook friends
        self.iq = iq
        self.mana = mana

    def befriend(self, other):
        self.ff += 1
        other.ff += 1

    def learn(self):
        self.iq += 1
    
    def magic_friends(self, num):
        self.ff += num if self.mana>0 else 0
        self.mana -= 100


vernon = Human("Vernon", 0, 80)
tom = Wizard("Tom", 666, 130, 100)
dumbledore = Wizard("Albus", 999, 189, 100)

dumbledore.befriend(tom)
dumbledore.befriend(vernon)
dumbledore.magic_friends(100)

print("Friends Vernon: " + str(vernon.ff))
print("Friends Tom: " + str(tom.ff))
print("Friends Dumbledore: " + str(dumbledore.ff))

Before you read on, I have two questions for you:

  1. What is the output of the above code snippet?
  2. What’s your idea of making this code more concise?

What is the output of the above code snippet?

Let’s start with the first question. We create two classes Human and Wizards. Both have very similar methods and attributes. The only difference is that the Wizard class has one additional attribute self.mana and one additional method magic_friends. Both methods befriend and magic_friends modify the instance attribute ff. With a magic trick, Dumbledore gains 100 additional friends, in addition to Tom and Vernon.

Thus, the result is:

"""
Friends Vernon: 1
Friends Tom: 667
Friends Dumbledore: 1101
"""

How to Improve This Code?

Regarding the second question, I have already indicated the problem: there are huge redundancies between the two classes Human and Wizard. Most methods and attributes are exactly the same. The reason is that, conceptually, a Wizard is also Human. And every human needs to have an IQ, a Facebook account, and a name (as you know).

In other words: Every Wizard is a Human but not every Human is a Wizard.

How can we express this fact in Python object orientation?

The answer is inheritance.

We create a class Human and a class Wizard. The class Wizard is the “child class” of the “parent class” Human. In this way, the child class Wizard “inherits” every attribute and method from the parent class Human. This saves us all the redundant definitions and initializations in the Wizard class.

See the example:

class Human:


    def __init__(self, name, ff, iq):
        self.name = name
        self.ff = ff # = facebook friends
        self.iq = iq

    def befriend(self, other):
        self.ff += 1
        other.ff += 1

    def learn(self):
        self.iq += 1



class Wizard(Human):


    def __init__(self, name, ff, iq, mana):
        super().__init__(name, ff, iq)
        self.mana = mana

    def magic_friends(self, num):
        self.ff += num if self.mana>0 else 0
        self.mana -= 100


vernon = Human("Vernon", 0, 80)
tom = Wizard("Tom", 666, 130, 100)
dumbledore = Wizard("Albus", 999, 189, 100)

dumbledore.befriend(tom)
dumbledore.befriend(vernon)
dumbledore.magic_friends(100)

print("Friends Vernon: " + str(vernon.ff))
print("Friends Tom: " + str(tom.ff))
print("Friends Dumbledore: " + str(dumbledore.ff))

dumbledore.learn()
print("IQ Dumbledore: " + str(dumbledore.iq))

The result is exactly the same as above. As you can see in the last two lines, Dumbledore can still call the method learn() — even though it is not defined in the Wizard class. The reason is that the Wizard class inherits all methods and attributes from the Human class.

Can you find where we define the inheritance in the code?

One final note: in the constructor of the Wizard class, we call the constructor of the parent class using "super()". This initializes the variables in exactly the same way as the parent constructor from the Human class.

Calling the Constructor of the Parent Class with super()

Python’s built-in super() method returns a temporary object of the superclass to help you access its methods. Its purpose is to avoid using the base class name explicitly. It also enables your class to inherit from multiple base classes.

The idea is simple: use super() to call the methods defined in the parent classes—whether your child class inherits from one or multiple base classes. See the graphic:

Python super() - Visual Explanation

Need me to guide you through this example? Watch the explainer video next!

Next, you’ll learn about both cases by example!

Simple Example 1: super() with Single Inheritance

Inheritance in object-oriented programming allows you to create a class hierarchy where one child class inherits all methods from another parent class. This simplifies the development of large software projects and avoids redundant code. You can learn more about the concept of inheritance in our blog tutorial here.

For example, the following code defines the parent class Organism and the child class Human. The child class uses super() to run the constructor method of the parent class.

class Organism:
    def __init__(self):
        print('I live')


class Human(Organism):
    def __init__(self):
        print('I am human')
        super().__init__()


alice = Human()

The output is:

I am human
I live

Here you call the base class Organism using the following code:

super().__init__()

A semantically equivalent code call would be:

Organism.__init__(self)

You call the __init__() method on the base class Organism and pass a reference to the calling instance as an argument. This way, you could also modify the internal attributes of the self instance within the base class’ constructor.

However, the advantage of using super().__init__() compared to ParentClass.__init__(self) is that you avoid calling the parent class explicitly. This is advantageous because it decouples the child from the parent class. For example, if you changed the name of the ParentClass to NewParentClass, the method using super() would be superior because it would still work while the method using ParentClass.__init__(self) would throw an error.

Example 2: super() with Multiple Inheritance

One of Python’s unique features compared to other programming languages is that it allows multiple inheritance.

Multiple inheritance means that a class can inherit from multiple parents. For example, a class Human can inherit from two parent classes: Organism and Thinker. Say, you define a method live() in Organism and think() in Thinker. If a Human object inherits from both classes, it can call live() and think() at the same time! You use the super() method to call those functions:

class Organism:
    def live(self):
        print('I live')


class Thinker:
    def think(self):
        print('I think')


class Human(Organism, Thinker):
    def __init__(self):
        print('I am human')
        super().live()
        super().think()
 

alice = Human()

The output is:

I am human
I live
I think

I should mention that in this example, you could also have called self.live() and self.think() in the class Human instead of super().live() and super().think(). The output would be the same in both cases. In practice, you’d use the former for instance methods and the latter for class methods. The difference between both is explained in our blog tutorial here.

Python OOP Cheat Sheet

Congratulations, you read through the whole article. Here’s a small reward for your effort: my object-orientation terminology cheat sheet!

Download this cheat sheet as PDF

You can also check out my in-depth Python cheat sheet tutorial with lots of free PDF cheat sheets about various topics in Python such as keywords, data structures, lists, sets, NumPy, pandas, and many more. Simply put your email here and download my free cheat sheets:

Where to Go From Here?

Strategy beats tactics in the long run. You need to have a learning strategy in place to master Python. Do you?

If not, get a quick win by downloading our popular Python cheat sheets (high-res PDF), as well as the long-term solution to your problem (almost daily Python lessons). It’s 100% free. No Spam. Guaranteed.