Python OOP Inheritance Tutorial

In this tutorial we learn how to inherit functionality from one class to another, without having to rewrite the code.

We cover the Is-A relationship of inheritance and how it allows us to reuse code from another class.

What is inheritance?

Inheritance allows us to define a sub or child class which inherits functionality from another class, otherwise known as a base class or a parent class.

Inheritance is usually referred to as an Is-A relationship. For example: a car is a vehicle, or a dog is an animal.

Inheritance allows us to reuse code from a class. We wrap common code in one class, then reuse that code in another class.

Inheritance also provides polymorphic behavior, which can be quite powerful.

How to create a child class

In Python, we need to pass the parent class as an argument to the definition of the child class.

Syntax:
class ParentClass:
    # body

class ChildClass(ParentClass):
    # body

As an example, let’s create an Animal class. Then, we’ll add two child classes that inherit from it.

Example:
class Animal:
    def __init__(self, legs):
        self.legs = legs

    def walk(self):
        print("Animal parent class. Walking...")

class Dog(Animal):
    def growl(self):
        print("Dog child class. A dog can growl but a duck can't. Grrr...")

class Duck(Animal):
    def quack(self):
        print("Duck child class. A duck can quack but a dog can't. Quack...")

Both child classes has access to Animal’s constructor, attributes and methods. But the instance methods defined in the child classes are only available to themselves.

  • The Dog class can use walk() from the parent and growl() from itself, but not quack() .
  • The Duck class can use walk() from the parent and quack() from itself, but not growl() .

Child classes and the __init__() constructor method

If we have something in the __init__() method of the parent class, we can pass values to it from a child class just like we normally would when creating an object.

Syntax:
class ParentClass:
    # body

class ChildClass(ParentClass):
    # body

object_name = ChildClass(init_value, init_value, ...)

As an example, let’s create a Dog instance and pass a value to its constructor.

Example:
class Animal:
    def __init__(self, legs):
        self.legs = legs

    def walk(self):
        print("Animal parent class. Walking...")

class Dog(Animal):
    def growl(self):
        print("Dog child class. A dog can growl but a duck can't. Grrr...")

class Duck(Animal):
    def quack(self):
        print("Duck child class. A duck can quack but a dog can't. Quack...")

fluffy = Dog(4)

print(fluffy.legs)

Because the parent has a constructor, our instance will use that. If any child class also defines their own constructor, it will override the one from the parent.

Example:
class Animal:
    def __init__(self, legs):
        self.legs = legs

    def walk(self):
        print("Animal parent class. Walking...")

class Dog(Animal):
    def __init__(self, tail):
        self.tail = tail

    def growl(self):
        print("Dog child class. A dog can growl but a duck can't. Grrr...")

    class Duck(Animal):
        def quack(self):
            print("Duck child class. A duck can quack but a dog can't. Quack...")

fluffy = Dog(True)

print(fluffy.legs)
print(fluffy.tail)

In the example above we still try to access the “legs” attribute but we can’t, the interpreter will raise an AttributeError.

Output:
 AttributeError: 'Dog' object has no attribute 'legs'

This is because we override the parent class constructor with the child class constructor.

If we still want the “legs” attribute, we can duplicate the code from the parent class to the child class.

Example:
class Animal:
    def __init__(self, legs):
        self.legs = legs

    def walk(self):
        print("Animal parent class. Walking...")

class Dog(Animal):
    def __init__(self, legs, tail):
        self.legs = legs
        self.tail = tail

    def growl(self):
        print("Dog child class. A dog can growl but a duck can't. Grrr...")

class Duck(Animal):
    def quack(self):
        print("Duck child class. A duck can quack but a dog can't. Quack...")

fluffy = Dog(4, True)
print(fluffy.legs)
print(fluffy.tail)

The example above will work because the child class also has the “legs” attribute. However, this defeats the purpose of setting up “legs” in the parent class in the first place. We want to avoid unnecessary duplication.

Refer to the parent class with the super() method

What we can do to solve the problem of duplication in our child class is use the super() method. This method is going to refer to the parent class of the child that inherits from it.

With the super() method we can specify any member from the parent class and bring it into the child class.

Syntax:
 super().method(value, value, ...)
Example:
class Animal:
    def __init__(self, legs):
        self.legs = legs

    def walk(self):
        print("Animal parent class. Walking...")

class Dog(Animal):
    def __init__(self, legs, tail):
        super().__init__(legs)
        self.tail = tail

    def growl(self):
        print("Dog child class. A dog can growl but a duck can't. Grrr...")

class Duck(Animal):
    def quack(self):
        print("Duck child class. A duck can quack but a dog can't. Quack...")

fluffy = Dog(4, True)
print(fluffy.legs)
print(fluffy.tail)

This may seem somewhat confusing so let’s go through it step by step.

  1. In the example above we still have the legs parameter in the __init__() method because we need it to pull the attribute from the parent class with super.
  2. Then we have super().__init__() to specify we want the __init__() method from the parent class.
  3. Finally, we specify that the legs parameter should pull the legs attribute from the parent class’s __init__() method.

So basically, we just have to super() the method and its parameters down into the child class.

Multiple Inheritance

note Multiple Inheritance isn’t used that often as it can become complex and confusing. In almost all situations there are easier or better ways of solving the problem. It’s included in this lesson for completeness.

Even though multiple inheritance is not commonly used, it may be useful to use in some cases. Multiple inheritance means that a child class inherits from more than one parent class.

You may have already guessed what the syntax will look like. We add both parent classes as parameters in the child class definition, separated by a comma.

Syntax:
class ParentClass1:
    # body

class ParentClass2:
    # body

class ChildClass(ParentClass1, ParentClass2):
    # body
Example:
class SeaAnimal:
    def __init__(self, name):
        self.name = name

    def swim(self):
        print("Swimming")

class LandAnimal:
    def __init__(self, name):
        self.name = name

    def walk(self):
        print("Walking...")

class Penguin(SeaAnimal, LandAnimal):
    def __init__(self, name):
        super().__init__(name)

In both parent classes we have a name attribute and then a movement method for each one, and the child class inherits from both parent classes.

When we create a Penguin child class object, we’ll have access to both the parent methods.

Example:
class SeaAnimal:
    def __init__(self, name):
        self.name = name

    def swim(self):
        print("Swimming...")

class LandAnimal:
    def __init__(self, name):
        self.name = name

    def walk(self):
        print("Walking...")

class Penguin(SeaAnimal, LandAnimal):
    def __init__(self, name):
        super().__init__(name)

penguin = Penguin("Kowalski")

print(penguin.name)
penguin.walk()
penguin.swim()

You may be wondering which parent class we’re pulling the “name” attribute from. The “name” attribute we’re using in the Penguin child class, comes from the SeaAnimal class.

This is because it’s first in the list of parameters in the Penguin class definition.

To demonstrate this, let’s create a simple print statement to show us which parent class’s __init__() method is executed.

Example:
class SeaAnimal:
    def __init__(self, name):
        print("SeaAnimal INIT")
        self.name = name

    def swim(self):
        print("Swimming...")

class LandAnimal:
    def __init__(self, name):
        print("LandAnimal INIT")
        self.name = name

    def walk(self):
        print("Walking...")

class Penguin(SeaAnimal, LandAnimal):
    def __init__(self, name):
        super().__init__(name)

penguin = Penguin("Kowalski")
penguin.walk()
penguin.swim()

Because the __init__() method from the SeaAnimal parent class already ran, it won’t run __init__() from LandAnimal.

We still have access to the method from the LandAnimal parent, it just didn’t run its constructor.

Pulling attributes from more than one parent

In most cases we are going to need to pull attributes from both parents instead of an attribute from the just first parent.

Instead of super() we specify explicitly which parent class we want to bring the attribute from. We also assign the parent attributes to their corresponding child attributes.

Example:
class SeaAnimal:
    def __init__(self, name):
        print("SeaAnimal INIT")
        self.name = name

    def swim(self):
        print("Swimming...")

class LandAnimal:
    def __init__(self, age):
        print("LandAnimal INIT")
        self.age = age

    def walk(self):
        print("Walking...")

class Penguin(SeaAnimal, LandAnimal):
    def __init__(self, name, age):
        SeaAnimal.__init__(self, name=name)
        LandAnimal.__init__(self, age=age)

penguin = Penguin("Kowalski", 3)

print(penguin.name, penguin.age)

Summary: Points to remember

  • Inheritance allow us to use functionality from other classes.
  • Inheritance is an Is-A relationship, a car is-a vehicle.
  • To inherit from a class we write its name between parentheses in our class definition.
  • We refer to the parent class with the super() method.
  • To inherit from multiple classes, we write their names, separated by a comma, between parentheses in our class definition.
  • When inheriting from multiple classes, we specify the class name explicitly.