W3docs

Python Inheritance

Learn Python inheritance: subclasses, method overriding, super(), multilevel and multiple inheritance, MRO, and isinstance checks — with runnable examples.

Understanding Python Inheritance

Inheritance lets you build a new class on top of an existing one. The new class (the subclass or child class) automatically gets all the attributes and methods of the existing class (the superclass, base class, or parent class). You can then add extra behaviour or selectively override what you inherited.

This chapter covers:

  • The syntax for creating subclasses
  • How super() works and why you should use it
  • Method overriding and calling the parent's version
  • Single, multilevel, and multiple inheritance
  • Python's Method Resolution Order (MRO)
  • isinstance() and issubclass() for runtime type checks
  • Common gotchas

Before reading this chapter, make sure you are comfortable with Python classes and objects. For related OOP pillars, see Python Encapsulation and Python Abstract Classes.

The Syntax for Inheritance

To create a subclass, write the parent class name inside parentheses after the new class name:

class ParentClass:
    pass

class ChildClass(ParentClass):
    pass

A minimal but concrete example with a Vehicle base class and a Car subclass:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        print(f"{self.make} {self.model} started.")

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)   # delegate to Vehicle.__init__
        self.num_doors = num_doors


camry = Car("Toyota", "Camry", 2023)
camry.start()   # Toyota Camry started.
camry.stop()    # Toyota Camry stopped.
print(camry.num_doors)  # 4

Car inherits start() and stop() from Vehicle without duplicating them. Adding num_doors is the only new concern Car has to handle.

Using super()

super() returns a proxy object that delegates method calls to the parent class. Its most common use is inside __init__ to initialise the parent's attributes before adding the child's own:

class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)  # runs Vehicle.__init__
        self.num_doors = num_doors

Why prefer super() over writing Vehicle.__init__(self, ...) directly?

  • Maintenance: if you rename or replace the parent class, you only change one line.
  • Multiple inheritance: super() follows Python's Method Resolution Order (MRO), so every class in the chain gets called correctly. Hard-coding the parent name skips that logic.

You can call super() for any method, not just __init__:

class Car(Vehicle):
    def start(self):
        super().start()                               # call Vehicle.start first
        print(f"({self.num_doors}-door model ready)")

Method Overriding

A subclass overrides a parent method by defining a method with the same name. Python always calls the most-derived version first:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        print(f"{self.make} {self.model} started.")

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def start(self):
        print(f"{self.make} {self.model} revved the engine and started.")


camry = Car("Toyota", "Camry", 2023)
camry.start()  # Toyota Camry revved the engine and started.
camry.stop()   # Toyota Camry stopped.  (inherited, not overridden)

When you want to extend rather than replace the parent behaviour, call super() inside the override:

class Car(Vehicle):
    def start(self):
        super().start()                         # Vehicle.start runs first
        print("Seatbelt reminder: buckle up!")  # then add the extra step

Inheriting Class Attributes

Inheritance applies to class attributes (shared across instances) as well. A subclass can override them the same way it overrides methods:

class Vehicle:
    wheels = 4

class Motorcycle(Vehicle):
    wheels = 2   # override the class attribute

class Car(Vehicle):
    pass         # inherits wheels = 4


print(Motorcycle.wheels)  # 2
print(Car.wheels)         # 4
print(Vehicle.wheels)     # 4

Types of Inheritance

Single Inheritance

One child, one parent — the most common pattern.

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

    def speak(self):
        return f"{self.name} makes a sound."


class Dog(Animal):
    def speak(self):
        return f"{self.name} says woof!"


dog = Dog("Rex")
print(dog.speak())  # Rex says woof!

Multilevel Inheritance

A subclass can itself be subclassed, creating a chain:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)
        self.num_doors = num_doors

    def start(self):
        print(f"{self.make} {self.model} started.")


class ElectricCar(Car):
    def __init__(self, make, model, year, range_km):
        super().__init__(make, model, year)   # calls Car.__init__
        self.range_km = range_km

    def start(self):
        print(f"{self.make} {self.model} powered up silently.")

    def charge(self):
        print(f"Charging... range is {self.range_km} km.")


tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start()   # Tesla Model 3 powered up silently.
tesla.charge()  # Charging... range is 580 km.
tesla.stop()    # Tesla Model 3 stopped.  (inherited from Vehicle)

ElectricCar sits two levels below Vehicle. Each call to super().__init__ moves one level up the chain, so all three __init__ methods run.

Multiple Inheritance

Python lets a class inherit from more than one parent. List them in the parentheses, separated by commas:

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

    def speak(self):
        return f"{self.name} makes a sound."


class Swimmer:
    def swim(self):
        return f"{self.name} swims."


class Flyer:
    def fly(self):
        return f"{self.name} flies."


class Duck(Animal, Swimmer, Flyer):
    def speak(self):
        return f"{self.name} says quack!"


donald = Duck("Donald")
print(donald.speak())  # Donald says quack!
print(donald.swim())   # Donald swims.
print(donald.fly())    # Donald flies.

Multiple inheritance is powerful but introduces complexity. Use mixins — small, single-purpose classes that add one capability — to keep it manageable. Swimmer and Flyer above are exactly that pattern.

The Method Resolution Order (MRO)

When Python looks up a method or attribute, it follows a deterministic search order called the Method Resolution Order (MRO). For single inheritance the order is obvious (child → parent → grandparent → object). For multiple inheritance Python uses the C3 linearisation algorithm to produce an unambiguous order.

Inspect the MRO of any class with .__mro__ or help():

print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)

The MRO matters most when multiple parents define the same method. Python picks the first class in the MRO that defines it. This is why super() is important in multiple-inheritance hierarchies: each class in the MRO calls super(), so every class in the chain gets a chance to run.

class Base:
    def greet(self):
        print("Hello from Base")


class Left(Base):
    def greet(self):
        print("Hello from Left")
        super().greet()


class Right(Base):
    def greet(self):
        print("Hello from Right")
        super().greet()


class Child(Left, Right):
    def greet(self):
        print("Hello from Child")
        super().greet()


Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from Base

Each call to super() follows the MRO (Child → Left → Right → Base), so every greet() runs exactly once despite Base appearing as a parent of both Left and Right. This is the diamond problem — Python's MRO solves it cleanly.

Checking Inheritance at Runtime

isinstance(obj, cls)

Returns True if obj is an instance of cls or any of its subclasses:

tesla = ElectricCar("Tesla", "Model 3", 2024, 580)

print(isinstance(tesla, ElectricCar))  # True
print(isinstance(tesla, Car))          # True  — Car is a parent
print(isinstance(tesla, Vehicle))      # True  — Vehicle is a grandparent
print(isinstance(tesla, str))          # False

This is more reliable than comparing type(obj) == Car, which returns False for subclasses.

issubclass(sub, cls)

Returns True if sub is a subclass of cls (including itself):

print(issubclass(ElectricCar, Vehicle))  # True
print(issubclass(Car, ElectricCar))      # False
print(issubclass(Car, Car))              # True  — a class is a subclass of itself

Common Gotchas

Forgetting to call super().__init__()

If the child's __init__ does not call super().__init__(), the parent's attributes are never set. Any method that expects them will raise AttributeError:

class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        # forgot super().__init__(...)
        self.num_doors = num_doors

c = Car("Toyota", "Camry", 2023)
c.start()  # AttributeError: 'Car' object has no attribute 'make'

Overriding without calling super() when you meant to extend

If you override a method and forget super(), the parent's version never runs. This silently drops behaviour that other code may depend on.

Deep multiple-inheritance hierarchies

More than two levels of multiple inheritance is very hard to reason about. If you find yourself writing class Foo(A, B, C, D), consider using composition — storing instances as attributes — instead.

Benefits of Inheritance

  1. Code reuse — shared logic lives in one place; subclasses inherit it for free.
  2. Extensibility — you can add or change behaviour in a subclass without touching the parent, keeping existing callers unchanged.
  3. Polymorphism — functions that accept a Vehicle work equally well with any Car, Motorcycle, or ElectricCar. See Python Polymorphism for the full picture.

Practice

Practice
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Was this page helpful?