Inheritance lets one class build on another. The new class — the subclass — gets everything from its parent for free, and can add or override behaviour.

A first inheritance

class Animal:
    def __init__(self, name: str) -> None:
        self.name = name

    def speak(self) -> str:
        return "Some sound"


class Dog(Animal):
    def speak(self) -> str:
        return "Woof"


class Cat(Animal):
    def speak(self) -> str:
        return "Meow"


d = Dog("Rex")
c = Cat("Whiskers")

print(d.name, d.speak())   # Rex Woof
print(c.name, c.speak())   # Whiskers Meow

Dog(Animal) says “Dog inherits from Animal”. The Dog class gets __init__ from Animal for free, but overrides speak.

This is the core idea: shared behaviour goes in the parent; specific differences go in each subclass.

Calling the parent — super()

Sometimes a subclass wants to extend behaviour, not replace it. Use super() to call the parent’s version:

class Animal:
    def __init__(self, name: str) -> None:
        self.name = name


class Dog(Animal):
    def __init__(self, name: str, breed: str) -> None:
        super().__init__(name)        # call Animal.__init__
        self.breed = breed


d = Dog("Rex", "Labrador")
print(d.name, d.breed)   # Rex Labrador

super() returns a reference to the parent class. Calling super().__init__(name) reuses the parent’s setup, then Dog.__init__ adds its own.

This pattern is everywhere in real code — __init__, file handlers, GUI widgets, framework base classes.

Inheritance vs composition

Inheritance is one way to share behaviour. The other is composition — holding an instance of another class as an attribute:

# composition
class Engine:
    def start(self) -> None:
        print("Engine starting")


class Car:
    def __init__(self) -> None:
        self.engine = Engine()       # has-an Engine, not is-an Engine

    def drive(self) -> None:
        self.engine.start()


c = Car()
c.drive()

The rule of thumb:

  • Inheritance — “a Dog is an Animal”
  • Composition — “a Car has an Engine”

Composition is more flexible and easier to refactor. Modern Python style leans toward composition unless inheritance genuinely fits.

isinstance and type checks

To check at runtime whether an object is an instance of a class (including subclasses):

d = Dog("Rex", "Labrador")

print(isinstance(d, Dog))      # True
print(isinstance(d, Animal))   # True — Dog inherits from Animal
print(isinstance(d, Cat))      # False

isinstance respects inheritance. Most of the time it’s what you want.

For strict equality, use type():

print(type(d) is Dog)      # True
print(type(d) is Animal)   # False

But type() is is rarely what you want. Prefer isinstance.

Method resolution

When you call d.speak(), Python looks for speak on Dog first; if not there, then Animal; then its parent, and so on. The first match wins.

This is called the MRO (method resolution order). For single inheritance, it’s a straight chain — easy to reason about. Multiple inheritance complicates it (next lesson — but kept brief).

You can see it:

print(Dog.__mro__)
# (<class 'Dog'>, <class 'Animal'>, <class 'object'>)

Every Python class ultimately inherits from object.

A common base — Exception

We saw this in Section 9. Custom exception classes inherit from Exception:

class HttpError(Exception):
    pass


class NotFoundError(HttpError):
    pass


try:
    raise NotFoundError("missing")
except HttpError as e:
    print("caught:", e)

The hierarchy lets callers catch broadly (HttpError) or narrowly (NotFoundError).

A note on multiple inheritance and ABCs

Python does allow a class to inherit from multiple parents:

class A: ...
class B: ...
class C(A, B): ...

This is usually a sign the design needs rethinking. The exception is mixin classes — small helper classes that add one specific capability. In modern Python, the cleaner alternative is usually Protocols (we’ll see in Section 13) or composition.

Abstract base classes (ABCs, in the abc module) let you declare a class that can’t be instantiated directly — only inherited. They’re used in libraries to define interfaces. We’ll keep this brief: just know they exist, and reach for Protocols first in your own code.

What’s next

Last lesson of the section — data classes, the way to write tiny classes with much less boilerplate.

Toggle theme (T)