Inheritance in depth
Use single, multilevel, and multiple inheritance deliberately, and understand the method resolution order that makes cooperative super calls work.
By the end, you’ll be able to…
- Distinguish single, multilevel, and multiple inheritance.
- Read a class's method resolution order.
- Write cooperative constructors that pass keyword arguments through super.
- Choose composition when a deep hierarchy would be fragile.
01Three shapes of inheritance
Single inheritance gives one child one parent. Multilevel inheritance extends the chain: a dog is a pet, and a pet is an animal. Multiple inheritance gives a class more than one direct parent. Each shape expresses a different claim about the domain, and each adds a different amount of coupling.
Prefer the shallowest hierarchy that communicates the relationship. A deep chain makes it hard to know which override runs and which constructor established which attribute. If the only shared element is a helper function, a module function or a composed collaborator is usually clearer than a new subclass.
02MRO is the lookup order
Python resolves attributes using the method resolution order, a linearization computed from the class and its parents. The __mro__ attribute shows the exact order. In a diamond, the same ancestor appears once, and cooperative super() calls walk to the next class in that order rather than jumping to a fixed parent.
This is why super() is not simply 'call my parent'. Each class passes control to the next implementation. If one class in the chain forgets to call super(), the classes after it may never run. Keep cooperative chains short, and write a test that exercises the entire path.
03Design cooperative constructors
A cooperative constructor accepts the arguments it needs, removes them from the keyword arguments, and passes the rest to super().__init__. Each class initializes its own state after the chain returns. This lets a class participate in multiple inheritance without knowing the full hierarchy.
Use keyword arguments for the shared channel and make required values explicit. If two parents both expect a positional argument with the same name, the chain becomes ambiguous. A small mixin with a single responsibility is easier to reason about than a base class that tries to initialize everything.
04Prefer contracts over deep trees
An abstract base class or a Protocol describes what a collaborator must provide. Callers can then depend on that contract instead of a concrete subclass. This keeps substitution honest: a replacement must satisfy the same behavior, not merely share an ancestor.
Composition remains the default when behavior varies independently. A report can compose a formatter and a storage backend without creating a subclass for every combination. Use inheritance when the domain has a stable subtype relationship and the parent contract is genuinely useful to every child.
The ideas worth keeping.
- Single, multilevel, and multiple inheritance express different relationships.
- super follows the MRO, not a fixed parent pointer.
- Cooperative constructors pass shared keyword arguments along the chain.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Extend a 2D vector into 3D
Create Vector2D with x and y, then Vector3D that inherits from it and adds z. Initialize the parent through super and print a 3D point.
Explore a worked solution
class Vector2D: def __init__(self, x, y): self.x = x self.y = yclass Vector3D(Vector2D): def __init__(self, x, y, z): super().__init__(x, y) self.z = zpoint = Vector3D(2, 3, 4)print(point.x, point.y, point.z)The subclass passes the shared coordinates to the parent and adds only its own state. Calling super is what guarantees x and y exist.
Build an animal hierarchy
Create Animal with a speak method, Pet that inherits from Animal and adds a name, and Dog that inherits from Pet and returns 'Woof'. Use super in Pet's constructor and print the result.
Explore a worked solution
class Animal: def speak(self): return "..."class Pet(Animal): def __init__(self, name): self.name = nameclass Dog(Pet): def speak(self): return "Woof"dog = Dog("Rex")print(dog.name, dog.speak())The chain is Animal -> Pet -> Dog. Pet stores the name, Dog overrides only the behavior that differs, and the inherited interface still works.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Extend a 2D vector into 3D
Create Vector2D with x and y, then Vector3D that inherits from it and adds z. Initialize the parent through super and print a 3D point.
point.x point.y point.z
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.