Chapters
Python114 chapters

Classes and objectsChapter 64 of 114

Inheritance

Build a class on top of another, and override what differs.

A subclass gets everything

Put the parent class in brackets. The child starts with all its methods:

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

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

class Dog(Animal):
    pass

rex = Dog("Rex")
print(rex.speak())
print(isinstance(rex, Dog), isinstance(rex, Animal))

Output

Rex makes a sound
True True

A Dog is an Animal. That phrase is the test for whether inheritance is the right tool.

Overriding

Define a method with the same name and the child's version wins:

Python
class Animal:
    def speak(self):
        return "some sound"

class Dog(Animal):
    def speak(self):
        return "woof"

class Cat(Animal):
    def speak(self):
        return "meow"

for animal in [Animal(), Dog(), Cat()]:
    print(animal.speak())

Output

some sound
woof
meow

super() calls the parent

Use it to extend rather than replace, especially in __init__:

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

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

rex = Dog("Rex", "collie")
print(rex.name, rex.breed)

Output

Rex collie

Forget the super().__init__(name) and self.name never gets set, so the first use raises AttributeError.

super() works for any method, not just __init__:

Python
class Animal:
    def describe(self):
        return "an animal"

class Dog(Animal):
    def describe(self):
        return super().describe() + ", specifically a dog"

print(Dog().describe())

Output

an animal, specifically a dog

Attribute lookup order

Python looks at the object, then its class, then up the chain of parents. The order is on the class itself:

Python
class Animal: pass
class Dog(Animal): pass

print([c.__name__ for c in Dog.__mro__])

Output

['Dog', 'Animal', 'object']

Every class inherits from object eventually, which is where the default __repr__ and friends come from.

Multiple inheritance

Python allows more than one parent. The MRO decides which method wins:

Python
class Swimmer:
    def move(self):
        return "swims"

class Runner:
    def move(self):
        return "runs"

class Dog(Runner, Swimmer):
    pass

print(Dog().move())
print([c.__name__ for c in Dog.__mro__])

Output

runs
['Dog', 'Runner', 'Swimmer', 'object']

Leftmost parent first. Multiple inheritance gets confusing quickly; mixins with no overlapping methods are the case where it stays manageable.

Prefer composition when it is not "is a"

A car is not an engine, so it should hold one rather than inherit from it:

Python
class Engine:
    def start(self):
        return "engine running"

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

    def start(self):
        return "car: " + self.engine.start()

print(Car().start())

Output

car: engine running

Composition is easier to change later, because nothing depends on the shape of a class you did not write.

Test yourself

2 questions

What does super().__init__(name) do in a subclass?

Show the answer

Runs the parent's initialiser so its setup still happens — Skip it and the attributes the parent sets never exist, so the first use raises AttributeError.

When is composition a better fit than inheritance?

Show the answer

When the relationship is 'has a' rather than 'is a' — A car has an engine; it is not one. Composition is also easier to change later.

Next chapter

Polymorphism

Different types answering the same call, and why Python barely notices.