Reusing Behaviour Without Copying It
Two classes that share eighty percent of their behaviour usually end up as two copies of that eighty percent. Write the shared part once, inherit it in both, and override only what actually differs.
Two classes that share eighty percent of their behaviour usually end up as two copies of that eighty percent. Write the shared part once, inherit it in both, and override only what actually differs.
A child class starts with everything the parent has
$ >>> class Animal:$ ... def __init__(self, name):$ ... self.name = name$ ... def describe(self):$ ... return f"{self.name} is an animal"$ ...$ >>> class Dog(Animal):$ ... pass$ ...$ >>> rex = Dog("Rex")$ >>> rex.describe()$ 'Rex is an animal'class Dog(Animal): makes Dog a subclass of Animal. Even though Dog defines nothing of its own yet, it already has __init__ and describe, inherited in full.
$ >>> isinstance(rex, Dog)$ True$ >>> isinstance(rex, Animal)$ TrueEvery Dog is also an Animal, as far as Python is concerned — isinstance confirms both at once. That single fact is what “is a kind of” means in code, not just in the way you would describe it in English.
Overriding a method without touching the original
Define a method with the same name in the subclass, and it replaces the parent's version for that subclass only — Animal itself, and anything else that inherits from it, is untouched.
$ >>> class Dog(Animal):$ ... def describe(self):$ ... return f"{self.name} is a dog"$ ...$ >>> rex = Dog("Rex")$ >>> rex.describe()$ 'Rex is a dog'Sometimes you do not want to replace the parent's version outright — you want the child's version to do its own thing and still run the parent's. Rewriting describe()'s whole body to include what Animal already did means copying that line; there is a way to avoid the copy.
Calling the parent's version with super()
super() gets you a reference to the parent class's version of a method, callable from inside the child's own override — without naming Animal directly, which matters more once a program has several levels of inheritance.
$ >>> class Dog(Animal):$ ... def describe(self):$ ... base = super().describe()$ ... return f"{base}, specifically a dog"$ ...$ >>> rex = Dog("Rex")$ >>> rex.describe()$ 'Rex is an animal, specifically a dog'__init__ is the most common place to reach for super(): a child that adds one new field, like a breed, still wants the parent to set up name the way it always has, rather than duplicating that line.
$ >>> class Dog(Animal):$ ... def __init__(self, name, breed):$ ... super().__init__(name)$ ... self.breed = breed$ ...$ >>> rex = Dog("Rex", "Labrador")$ >>> rex.name$ 'Rex'$ >>> rex.breed$ 'Labrador'When inheritance is the right tool, and when composition is
Inheritance says this thing is a kind of that thing — a dog is a kind of animal. It fits badly the moment the relationship is really this thing has one of those instead — a car is not a kind of engine, it has an engine. Forcing that second shape into inheritance tends to produce a class hierarchy that fights the problem rather than describing it.
Reach for inheritance
Reach for composition
$ >>> class Engine:$ ... def start(self):$ ... return "vroom"$ ...$ >>> class Car:$ ... def __init__(self):$ ... self.engine = Engine()$ ... def start(self):$ ... return self.engine.start()$ ...$ >>> Car().start()$ 'vroom'Car does not inherit from Engine — it holds one, stored as an ordinary attribute, and start() delegates to it. Nothing about Car's own methods needs to know how the engine actually works, and swapping in an ElectricEngine later means changing one line, not restructuring a class hierarchy.
Key takeaways
- class Dog(Animal): makes Dog a subclass that inherits everything Animal defines — isinstance(rex, Animal) is true even though rex was built as a Dog.
- Defining a method with the same name in the subclass overrides it there only. The parent class, and anything else that inherits from it, is untouched.
- super() calls the parent's version of a method from inside a child's override, most often inside __init__, so the child doesn't have to copy a line the parent already wrote.
- Use inheritance when the relationship is "is a kind of". Use composition — storing another object as a plain attribute — when it's "has a" instead.
- Python allows inheriting from more than one parent at once. It's legal, but which parent's method wins when both define one is decided by Python's method resolution order.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.