☰

Python Inheritance Tutorial

Python Inheritance Tutorial: Types, super() and Examples

Imagine you are writing a program for a sports shop. You write a class for sports items with a model, a colour and a cost. Next, you need a class for shoes, which has all of those and a brand as well. Do you copy the first class and add one line? You could, but then every fix must be made twice, and one copy will soon be wrong. Inheritance is the way out.

Inheritance lets a new class take everything an existing class has, and then add or change only what is different. The existing class is called the parent, and the new one is the child. It is one of the four main ideas of object oriented programming, with encapsulation, polymorphism and abstraction, and you will find it in almost every large Python project.

In this tutorial, we begin with a tiny working example and then build a real one with a parent, a child and super(). We cover all the types of inheritance with tested code, method overriding, the method resolution order, and the mistakes that cause errors. Everything below was run, and the output shown is the real output.

What is Inheritance in Python?

Inheritance is the ability of one class to use the attributes and methods of another class. The class that gives is the parent class, also called the superclass or base class. The class that receives is the child class, also called the subclass or derived class.

The main benefits are:

  • Reuse. Write the common code once, in the parent, and every child gets it.
  • One place to fix. A bug fixed in the parent is fixed for all children.
  • Easy to extend. A child can add new methods, or change inherited ones, without touching the parent.
  • Transitive. If C inherits from B, and B inherits from A, then C also gets everything from A.

A simple test for good use: the sentence child is a kind of parent should sound right. A dog is a kind of animal, so Dog can inherit from Animal. Shoes are a kind of sports item, so that works too.

How to Create a Child Class

Put the name of the parent in brackets after the name of the child class.

python
class ChildClassName(ParentClassName):
    # body of the child class

Here is the smallest working example. The child class does nothing of its own, and it still can use the parent method:

python
class Parent:
    def greet(self):
        print("Hello from the parent class")

class Child(Parent):
    pass

Child().greet()
output
Hello from the parent class

The word pass means do nothing, and it is needed because a class cannot be empty. Notice the indentation, which is four spaces. Python stops with an error when it is missing:

python
class X:
def f(self):
    pass
output
IndentationError: expected an indented block after class definition on line 1

Python Inheritance Example: SportsItem and Shoes

Now a real one. The parent class SportsItem stores a model, a colour and a cost. Its child Shoes adds a brand. Shoes does not repeat any of the parent code.

python
class SportsItem:
    def __init__(self, model, color, cost):
        self.__model = model
        self.__color = color
        self.__cost = cost

    def get_model(self):
        return self.__model

    def get_color(self):
        return self.__color

    def set_color(self, color):
        self.__color = color

    def get_cost(self):
        return self.__cost

    def set_cost(self, cost):
        self.__cost = cost


class Shoes(SportsItem):
    def __init__(self, model, color, cost, brand):
        super().__init__(model, color, cost)
        self.__brand = brand

    def info(self):
        return (self.get_model() + " of brand " + self.__brand + " in "
                + self.get_color() + " color for Rs." + str(self.get_cost()))

sh1 = Shoes("Shoes", "Black", 3699, "Xyz123")
print(sh1.info())
print(sh1.get_model())
sh2 = Shoes("Shoes", "Black red", 4599, "Xyz345")
print(sh2.info())
sh2.set_cost(3999)
print(sh2.info())
output
Shoes of brand Xyz123 in Black color for Rs.3699
Shoes
Shoes of brand Xyz345 in Black red color for Rs.4599
Shoes of brand Xyz345 in Black red color for Rs.3999

Notes on the Code

  • There is one set_color and one set_cost method.
  • Method names use lower case with underscores, the usual Python style.
  • The cost is stored as a number and is turned into text only in the output.

The Private Name Trap

Look at the double underscore in self.__model. Python changes such a name inside the class, so it is hard to reach from outside, and that includes child classes. A child must use the parent methods, such as get_model, to read it. Here is what happens when a child tries to read it directly:

python
class BadShoes(SportsItem):
    def show(self):
        return self.__model

BadShoes("Shoes", "Black", 3699).show()
output
AttributeError: 'BadShoes' object has no attribute '_BadShoes__model'

If you want a child to read a value directly, use a single underscore, such as self._model. It is a signal to other programmers that the value is for internal use, and Python does not rename it.

How super() Works in Python

The function super() gives you access to the parent class from inside the child. You use it for two main jobs.

  • To run the parent constructor. In the Shoes class, super().__init__(model, color, cost) lets the parent set up the model, colour and cost, so the child only deals with the brand.
  • To call a parent method that the child has changed. This is shown in the next section.

If a child writes its own __init__ and forgets to call the parent one, the parent values are never set. Python does not warn you. The error shows up later, when you use them:

python
class ForgetfulShoes(SportsItem):
    def __init__(self, brand):
        self.brand = brand

ForgetfulShoes("Xyz").get_model()
output
AttributeError: 'ForgetfulShoes' object has no attribute '_SportsItem__model'

Method Overriding

A child can write a method with the same name as one in the parent. The child version is used for child objects. This is called overriding. Inside it, super() lets you keep the parent behaviour and add to it.

python
class Animal:
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return super().speak() + ", then Woof"

print(Animal().speak())
print(Dog().speak())
output
Some sound
Some sound, then Woof

isinstance and issubclass

Two built in functions check the family tree. The first checks an object, and the second checks a class.

python
d = Dog()
print(isinstance(d, Dog), isinstance(d, Animal), isinstance(Animal(), Dog))
print(issubclass(Dog, Animal), issubclass(Animal, Dog))
output
True True False
True False

A Dog object is also an Animal, but an Animal is not a Dog. The relation works in one direction only.

Types of Inheritance in Python

Python programmers usually talk about five types.

Types of inheritance in Python
Type Meaning Example
Single One child, one parent Dog inherits from Animal
Multilevel A chain: a grandparent, a parent and a child C inherits from B, and B from A
Hierarchical Several children, one parent Square and Circle inherit from Shape
Multiple One child, two or more parents MultiInher inherits from Super1 and Super2
Hybrid A mix of the types above Bottom inherits from Left and Right, which both inherit from Base

Single Inheritance

This is the case we have already seen in Child and Parent, Dog and Animal, and Shoes and SportsItem.

Multilevel Inheritance

Each class inherits from the one before it. So C gets everything from B and A.

python
class A:
    def who(self):
        return "A"

class B(A):
    pass

class C(B):
    pass

print(C().who())
print([k.__name__ for k in C.__mro__])
output
A
['C', 'B', 'A', 'object']

The second line shows the chain Python follows when it looks for a method. We explain it below.

Hierarchical Inheritance

Several children share one parent, and each changes the part that is different.

python
class Shape:
    def area(self):
        return 0

class Square(Shape):
    def __init__(self, s):
        self.s = s
    def area(self):
        return self.s * self.s

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return round(3.14159 * self.r * self.r, 2)

for sh in (Square(4), Circle(3)):
    print(type(sh).__name__, sh.area())
output
Square 16
Circle 28.27

Multiple Inheritance

A child can have more than one parent. List them in brackets, separated by commas. It is not a list, it is a group of names written in the class line.

python
class Super1:
    def __init__(self):
        self.test1 = "Python"
        print("Superclass1")

class Super2:
    def __init__(self):
        self.test2 = "Tutorials"
        print("Superclass2")

class MultiInher(Super1, Super2):
    def __init__(self):
        Super1.__init__(self)
        Super2.__init__(self)
        print("Derived")

    def print_vals(self):
        print(self.test1, self.test2)

obj = MultiInher()
obj.print_vals()
output
Superclass1
Superclass2
Derived
Python Tutorials

Here the child calls each parent constructor by name. That works for a simple case. For bigger programs, the next section shows the cleaner way.

Hybrid Inheritance and the Diamond

Hybrid inheritance mixes the types. The best known shape is the diamond, where two classes inherit from the same base, and a fourth class inherits from both. Python handles it with super(), and each class calls the next one in line:

python
class Base:
    def hello(self):
        return "Base"

class Left(Base):
    def hello(self):
        return "Left then " + super().hello()

class Right(Base):
    def hello(self):
        return "Right then " + super().hello()

class Bottom(Left, Right):
    def hello(self):
        return "Bottom then " + super().hello()

print(Bottom().hello())
output
Bottom then Left then Right then Base

Notice that Base runs only once, and that Right runs even though Left does not name it. This is the method resolution order at work.

Method Resolution Order (MRO)

When you call a method, Python searches the classes in a fixed order, and uses the first match. That order is the method resolution order. Print it with the __mro__ attribute:

python
print([k.__name__ for k in Bottom.__mro__])
output
['Bottom', 'Left', 'Right', 'Base', 'object']

Read it from left to right. A child comes before its parents, and the parents appear in the order you wrote them in the brackets. At the end of every chain sits object, the base of all classes in Python 3. So, if two parents define the same method, the one written first in the class line wins.

Inheritance or Composition?

Inheritance is not always the right tool. Use it when the child really is a kind of the parent. Use composition when one thing has another. A car has an engine, but a car is not an engine, so the car holds an engine object:

python
class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()
    def start(self):
        return self.engine.start()

print(Car().start())
output
Engine started

A good rule for new programmers: if the sentence is a kind of does not sound right, do not use inheritance.

Common Inheritance Errors and How to Fix Them

Inheritance errors and fixes
Error Usual cause Fix
IndentationError: expected an indented block The class or method body is not indented Indent the body by four spaces
SyntaxError on classabc or defgetModel A missing space after class or def Write class abc and def getModel
SyntaxError on a curly quote Text copied from a web page or a Word file Type straight quotes in your editor
AttributeError: object has no attribute _Class__name A double underscore name used in a child, or the parent init was not called Use the parent method, or call super().__init__()
TypeError: missing required positional argument The child init does not pass all values to the parent Pass every value that the parent init needs
TypeError: Cannot create a consistent method resolution order (MRO) A parent is listed before its own child, such as class C(A, B) when B inherits from A Put the child first, as in class C(B, A)

Practice Exercises

  1. Create a Vehicle class with a name and a speed. Create a Car class that inherits from it and adds a number of doors.
  2. Add a describe method to Vehicle, then override it in Car, and use super() to keep the first part of the text.
  3. Add a Bike class as a second child of Vehicle, and loop over a Car and a Bike calling describe on each.
  4. Print the method resolution order of Car, and check with isinstance that a Car object is also a Vehicle.
  5. Build a diamond like the one above, then swap the order of the two parents and see how the output changes.

Complete Script to Run Yourself

Here is the full script that was used to test every example on this page. It ran on Python 3.13.

python
import sys

# 1. Smallest example
class Parent:
    def greet(self):
        print("Hello from the parent class")

class Child(Parent):
    pass

Child().greet()

# 2. Sports example
class SportsItem:
    def __init__(self, model, color, cost):
        self.__model = model
        self.__color = color
        self.__cost = cost

    def get_model(self):
        return self.__model

    def get_color(self):
        return self.__color

    def set_color(self, color):
        self.__color = color

    def get_cost(self):
        return self.__cost

    def set_cost(self, cost):
        self.__cost = cost


class Shoes(SportsItem):
    def __init__(self, model, color, cost, brand):
        super().__init__(model, color, cost)
        self.__brand = brand

    def info(self):
        return (self.get_model() + " of brand " + self.__brand + " in "
                + self.get_color() + " color for Rs." + str(self.get_cost()))


sh1 = Shoes("Shoes", "Black", 3699, "Xyz123")
print(sh1.info())
print(sh1.get_model())
sh2 = Shoes("Shoes", "Black red", 4599, "Xyz345")
print(sh2.info())
sh2.set_cost(3999)
print(sh2.info())

# 3. Private name from a child
class BadShoes(SportsItem):
    def show(self):
        return self.__model

try:
    BadShoes("Shoes", "Black", 3699).show()
except AttributeError as e:
    print("AttributeError:", e)

# 4. Forgetting super().__init__
class ForgetfulShoes(SportsItem):
    def __init__(self, brand):
        self.brand = brand

try:
    ForgetfulShoes("Xyz").get_model()
except AttributeError as e:
    print("AttributeError:", e)

# 5. Overriding and super()
class Animal:
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return super().speak() + ", then Woof"

print(Animal().speak())
print(Dog().speak())

# 6. isinstance and issubclass
d = Dog()
print(isinstance(d, Dog), isinstance(d, Animal), isinstance(Animal(), Dog))
print(issubclass(Dog, Animal), issubclass(Animal, Dog))

# 7. Types
class A:
    def who(self):
        return "A"

class B(A):
    pass

class C(B):
    pass

print(C().who())
print([k.__name__ for k in C.__mro__])

class Shape:
    def area(self):
        return 0

class Square(Shape):
    def __init__(self, s):
        self.s = s
    def area(self):
        return self.s * self.s

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return round(3.14159 * self.r * self.r, 2)

for sh in (Square(4), Circle(3)):
    print(type(sh).__name__, sh.area())

class Base:
    def hello(self):
        return "Base"

class Left(Base):
    def hello(self):
        return "Left then " + super().hello()

class Right(Base):
    def hello(self):
        return "Right then " + super().hello()

class Bottom(Left, Right):
    def hello(self):
        return "Bottom then " + super().hello()

print(Bottom().hello())
print([k.__name__ for k in Bottom.__mro__])

# 8. Multiple inheritance
class Super1:
    def __init__(self):
        self.test1 = "Python"
        print("Superclass1")

class Super2:
    def __init__(self):
        self.test2 = "Tutorials"
        print("Superclass2")

class MultiInher(Super1, Super2):
    def __init__(self):
        Super1.__init__(self)
        Super2.__init__(self)
        print("Derived")

    def print_vals(self):
        print(self.test1, self.test2)

obj = MultiInher()
obj.print_vals()

# 9. Composition
class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()
    def start(self):
        return self.engine.start()

print(Car().start())

# 10. Unindented code
try:
    compile("class X:\ndef f(self):\n    pass", "x", "exec")
except IndentationError as e:
    print("IndentationError:", e.msg)

print(sys.version_info[:2])

Inheritance saves the most when the parent is small and clear, and the children change only a little. Start with single inheritance and super(), and add the other types only when a real problem calls for them. Type the examples, break them on purpose, and read the errors. A good next step is to learn about abstract classes and polymorphism, which build on what you learned here.

Popular Tags:

online course on python online python programming course online python training course Python Inheritance Tutorial