Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~26 min· topic 5 of 12

Topic 8.5

Inheritance and super()

In one line

Inheritance lets a new class (the subclass) start from an existing class (the parent) and add or change only what is different. super() reaches the parent's version of a method, and isinstance and issubclass ask whether something belongs to a family of classes.

Think of it like this

A family recipe book. Your grandmother has a recipe for plain bread. Your uncle's recipe says: "Make grandmother's bread, but add garlic." He does not copy the whole recipe; he points to hers and states what changes. A subclass is the uncle's recipe: it inherits everything from the parent class and says only what is different.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Inheritance
A class getting the attributes and methods of another class automatically.
Parent (base, super) class
The class being inherited from.
Subclass (child)
The class that inherits. Dog in class Dog(Animal).
Override
Define a method in a subclass with the same name as the parent's, to replace its behaviour.
`super()`
A way to call the parent class's version of a method from the subclass.
Polymorphism
The same method call giving different results depending on the object's class.
is-a
The relationship inheritance models: a Dog is an Animal.
`isinstance`
Built-in check: is this object an instance of this class or any subclass of it?

Step by step

01Start with a parent

Animal has a name, a speak method that returns "..." and an intro method that builds a sentence using self.speak(). On its own an Animal is generic and says "...".

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

    def speak(self):
        return "..."

    def intro(self):
        return self.name + " says " + self.speak()

02A subclass overrides what differs

Cat(Animal) only defines speak. It gets __init__ and intro for free. Dog(Animal) overrides both __init__ (to add a trick) and speak.

Calling pet.intro() on a cat prints "Tom says Meow": intro is inherited, but self.speak() finds the cat's version first.

A subclass overrides what differsdiagram
Rendering diagram…

03super().__init__ keeps the parent's setup

Dog's __init__ needs a name (the parent's job) and a trick (its own). It calls super().__init__(name) first and then sets self.trick.

super() with no arguments works inside a method. It finds the next class after the current one in the method resolution order and binds it to self. With a single parent, that is simply the parent.

main.pywhole filepython
class Dog(Animal):
    def __init__(self, name, trick):
        super().__init__(name)
        self.trick = trick

04Forgetting super() leaves holes

If Dog.__init__ does not call super().__init__, then self.name is never set. The program runs until something reads rex.name, and then fails with AttributeError. The code section shows a smaller version of this bug.

05Asking about families: isinstance and issubclass

isinstance(rex, Animal) is True because a Dog is an Animal. isinstance(rex, Cat) is False. issubclass(Dog, Animal) is True but issubclass(Animal, Dog) is False: the relationship points one way.

type(rex) is Animal is False but type(rex) is Dog is True. Use type(x) is only when you must exclude subclasses.

06Calling a parent method and extending it

Override and extend in one go: def speak(self): return super().speak() + " Woof". You reuse the parent's answer and add to it. This is common when a subclass must keep the parent's behaviour and add a step.

07When not to inherit

A Stack that inherits from list also inherits insert, sort and remove, which break the idea of a stack. A Stack that holds a list inside and offers only push, pop and len is safer. If you cannot say "X is a Y" and mean it for every method, do not inherit.

Try it yourself

  1. 1

    Add a new animal

    Add class Bird(Animal) with speak returning "Tweet", and put a Bird in the pets list. You do not touch the loop, yet it works for the new class. That is polymorphism paying off.

  2. 2

    Extend instead of replace

    Change Dog.speak to return super().speak() + " Woof" and predict the output before you run it. Which text comes from the parent?

  3. 3

    Remove the super call

    Delete the line super().__init__(name) from Dog.__init__ and run. Read the AttributeError carefully: it appears at the first line that reads self.name, not at the line you deleted.

Code & diagrams

Animals: override, super and isinstance New tab

Add a Bird class with its own speak and a loop that prints every pet's intro. Notice how the same call works for each class.

Sign in to run this example in your browser.

Expected output

Rex says Woof
Tom says Meow
Thing says ...
True True False
True False
False True
sit
Animal
True True
Inheriting too much, and forgetting super New tab

The first class lets someone call insert on a stack. The second shows what a skipped super().__init__() leaves behind.

Sign in to run this example in your browser.

Expected output

bad stack: [99, 1, 2, 3]
good size: 3 pop: 3
has insert? False
['extra']
False
The class familydiagram
Rendering diagram…
A realistic hierarchy: exporters that share a skeleton New tab

Animals are a toy. Here the parent owns the steps that never change (check, then render) and each subclass supplies only what differs. StrictCsvExporter extends the parent's check with super() instead of replacing it. Add an XmlExporter or a rule that rejects negative totals.

Sign in to run this example in your browser.

Expected output

CsvExporter
id,total
1,250
2,90
JsonExporter
[{"id": 1, "total": 250}, {"id": 2, "total": 90}]
ValueError: rows have different columns
True

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Skip the parent's __init__

Override __init__ in the subclass without calling super().__init__.

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


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


rex = Dog("Rex", "sit")
print(rex.name)
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 12, in <module>
print(rex.name)
^^^^^^^^
AttributeError: 'Dog' object has no attribute 'name'

Myth vs fact

Myth

Inheritance is the main way to reuse code in Python.

Fact

It is one way, and often not the best. Functions, modules and composition reuse code with fewer ties between classes. Use inheritance for real is-a relationships.

Myth

super() means the parent class.

Fact

It means the next class in the method resolution order. With single inheritance that is the parent, but with several parents it can be a sibling (Topic 8.6).

Myth

A subclass must always override __init__.

Fact

If the subclass needs no new setup, leave __init__ out. It inherits the parent's, and Cat("Tom") just works.

Myth

type(x) == Class and isinstance(x, Class) are the same.

Fact

isinstance also accepts subclasses. A Dog is an instance of Animal, but its type is not Animal.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The Liskov substitution principle says a subclass must work anywhere its parent does. A Square(Rectangle) that breaks set_width for rectangles violates it. If a subclass must restrict or reject what the parent allows, inheritance is the wrong tool.

  • ▸

    Zero-argument super() works because the compiler gives every method that mentions super a hidden __class__ cell, and Python takes the first argument as the instance. That is why it fails in a plain function defined outside a class body, and why a @staticmethod (no first argument) needs the explicit form super(Cls, obj).

  • ▸

    Overriding a method with a different signature is allowed and runs, but it breaks callers that expect the parent's. Keep the same parameters, or accept the same with extra optional ones. Type checkers (Phase 10) warn about this.

  • ▸

    Python 3.12 added typing.override (a decorator that marks a method as intended to override) so a type checker can catch a typo in the method name. Without it, a misspelt spaek silently creates a new method instead.

  • ▸

    Inheriting from built-ins like list or dict is subtle: some of their C-implemented methods do not call your overrides (for example, overriding dict.__setitem__ does not affect dict.update or the constructor). For custom containers prefer collections.UserList / UserDict or the abstract classes in collections.abc (Topic 8.10).

Remember this

  1. 1

    You write class Dog(Animal): to make Dog a subclass of Animal. Every Dog object now has all of Animal's methods and can add its own. The parent is also called the base class or superclass. This relationship is an is-a relationship: a dog is an animal.

  2. 2

    A subclass can override a method by defining one with the same name. When you call rex.speak(), Python looks on Dog first (Topic 8.2's lookup order, extended to parents), finds speak there and stops. Animal.speak is never reached. The inherited intro method still calls self.speak(), and because self is a Dog, it gets the dog's version. That is polymorphism: the same call does the right thing for each kind of object.

  3. 3

    super() gives you access to the parent's version of a method from inside the subclass. The most important use is in __init__: super().__init__(name) runs the parent's setup so inherited attributes get created. If you override __init__ and forget this call, the parent's attributes never appear, and you get AttributeError later.

  4. 4

    isinstance(obj, Class) is True if obj was built from Class or any subclass of it. issubclass(Dog, Animal) asks the same question about classes. Prefer isinstance over type(obj) is Class, because the second rejects subclasses. Remember that bool is a subclass of int (Topic 1.4), so isinstance(True, int) is True.

  5. 5

    Inheritance is a strong tie. A subclass depends on the parent's internals, so a change to the parent can break every child. A famous guideline says to prefer composition over inheritance: hold an object as an attribute instead of becoming one. Inherit when there is a true is-a relationship and the child can be used wherever the parent can. Do not inherit just to reuse code.

  6. 6

    Every class in Python inherits from object at the top, directly or indirectly. That is where __init__, __repr__, __eq__ and others come from by default. Dog.__bases__ lists the direct parents. Topic 8.6 covers what happens with more than one parent.

Explain it without notes

01

What is inheritance, and what does class Dog(Animal) mean?

02

What does super() do, and why call super().__init__() in a subclass?

03

What is polymorphism? Give an example with inheritance.

04

When should you not use inheritance?

Practice

01

Write a Shape class with a method area() that returns 0, and subclasses Square(side) and Circle(radius) that override it (use 3.14). Print the areas of a list of one of each.

02

Write a Vehicle with __init__(self, wheels), and a Car(Vehicle) whose __init__ takes brand, calls super().__init__(4), and stores the brand. Print car.wheels and car.brand.

03

Use isinstance to write describe(x) that returns "number" for ints and floats, "text" for strings and "other" for anything else. Test describe(True) and explain the result.

Trade-offs

  • ↔

    Inheritance gives free reuse and polymorphism, but couples child to parent. A change in the parent can break every subclass. Keep hierarchies shallow and parents stable.

  • ↔

    Composition is more code up front but easier to change. Holding a helper object lets you swap it, test it alone and avoid inheriting methods you do not want.

Done when you can

  • Done when you can write a subclass that overrides a method and one that extends it with super().

  • Done when you can explain why a subclass __init__ should call super().__init__().

  • Done when you can use isinstance and issubclass and explain why isinstance(True, int) is True.

  • Done when you can explain polymorphism using a loop over mixed objects.

  • Done when you can name a case where composition is better than inheritance.