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.
Doginclass 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 "...".
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.
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.
class Dog(Animal):
def __init__(self, name, trick):
super().__init__(name)
self.trick = trick04Forgetting 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
Add a new animal
Add
class Bird(Animal)withspeakreturning"Tweet", and put aBirdin thepetslist. You do not touch the loop, yet it works for the new class. That is polymorphism paying off. - 2
Extend instead of replace
Change
Dog.speaktoreturn super().speak() + " Woof"and predict the output before you run it. Which text comes from the parent? - 3
Remove the super call
Delete the line
super().__init__(name)fromDog.__init__and run. Read theAttributeErrorcarefully: it appears at the first line that readsself.name, not at the line you deleted.
Code & diagrams
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.
Expected output
Rex says Woof
Tom says Meow
Thing says ...
True True False
True False
False True
sit
Animal
True TrueThe first class lets someone call insert on a stack. The second shows what a skipped super().__init__() leaves behind.
Expected output
bad stack: [99, 1, 2, 3]
good size: 3 pop: 3
has insert? False
['extra']
FalseAnimals 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.
Expected output
CsvExporter
id,total
1,250
2,90
JsonExporter
[{"id": 1, "total": 250}, {"id": 2, "total": 90}]
ValueError: rows have different columns
TrueBreak 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__.
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)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 breaksset_widthfor 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 mentionssupera 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 formsuper(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 misspeltspaeksilently creates a new method instead. - ▸
Inheriting from built-ins like
listordictis subtle: some of their C-implemented methods do not call your overrides (for example, overridingdict.__setitem__does not affectdict.updateor the constructor). For custom containers prefercollections.UserList/UserDictor the abstract classes incollections.abc(Topic 8.10).
Remember this
- 1
You write
class Dog(Animal):to makeDoga subclass ofAnimal. EveryDogobject now has all ofAnimal'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
A subclass can override a method by defining one with the same name. When you call
rex.speak(), Python looks onDogfirst (Topic 8.2's lookup order, extended to parents), findsspeakthere and stops.Animal.speakis never reached. The inheritedintromethod still callsself.speak(), and becauseselfis aDog, it gets the dog's version. That is polymorphism: the same call does the right thing for each kind of object. - 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 getAttributeErrorlater. - 4
isinstance(obj, Class)isTrueifobjwas built fromClassor any subclass of it.issubclass(Dog, Animal)asks the same question about classes. Preferisinstanceovertype(obj) is Class, because the second rejects subclasses. Remember thatboolis a subclass ofint(Topic 1.4), soisinstance(True, int)isTrue. - 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
Every class in Python inherits from
objectat 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
What is inheritance, and what does class Dog(Animal) mean?
What does super() do, and why call super().__init__() in a subclass?
What is polymorphism? Give an example with inheritance.
When should you not use inheritance?
Practice
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.
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.
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 callsuper().__init__().Done when you can use
isinstanceandissubclassand explain whyisinstance(True, int)isTrue.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.