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.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

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
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...")

In the example above we create two child classes, Dog and Duck, with Animal as their parent class.

The child classes have access to the __init__() method, its instance parameter(s) and the walk() instance method.

The instance methods defined in the child classes are only available to themselves. The Dog class won’t be able to use the quack() instance method.

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, ...)
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)

If we define an __init__() method within the child class, it will no longer use the __init__() method from the parent 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, 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 overwrite the parent class __init__() method with the child class __init__() method.

What we can do is 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.

It is duplication and when programming 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. The super() 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.

We must call the super function with parentheses before the dot notation. It must be super().method() and not super.method()

Multiple Inheritance

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 will have access to both the parent methods, swim() and walk().

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 are 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__() the from LandAnimal.

We still have access to the walk() method from the LandAnimal parent, it just didn’t run its __init__() method.

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.

To do this we replace the super() method with the name of the class.

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)

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.

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.