Lesson 17: Inheritance & Polymorphism
Deep-dive into inheritance, method overriding, polymorphism, isinstance, and the MRO.
Inheritance — Code Reuse Down the Tree
A child class inherits every attribute and method from its parent, then adds or overrides what it needs. This eliminates duplication and expresses "is-a" relationships: a Dog is an Animal, a Car is a Vehicle.
Inheritance is the difference between copying code and reusing code. When three classes share the same __init__ pattern, you move that pattern to a parent once — and every child inherits it for free.
What You'll Learn in This Lesson
- Override parent methods and call
super() - Use polymorphism — one interface, many behaviors
- Check types with
isinstance()andissubclass() - Understand the Method Resolution Order (MRO)
Overriding — Child Writes Its Own Version
Redefining a parent method in the child is overriding. The child's version wins for child instances. Call the parent's version with super().method():
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "...generic sound..."
class Dog(Animal):
def speak(self): # OVERRIDE
return "Woof!"
Override + super() keeps parent logic while extending it — never copy-paste parent code into the child.
The pattern: the child's speak replaces the parent's for Dog instances, while Animal instances keep the generic version. Both methods coexist; which one runs depends on the object's actual class.
Polymorphism — Many Forms, One Interface
Polymorphism lets different classes be used through the same interface. If both Dog and Cat have a .speak() method, a loop can call .speak() on each without caring which class it is:
animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for animal in animals:
print(f"{animal.name}: {animal.speak()}")
Rocky: Woof!
Whiskers: Meow!
Mystery: ...generic sound...
"Call the method, let the object decide." Python doesn't check types before calling — it just tries the method. This is called duck typing: if it walks like a duck and quacks like a duck, it's a duck.
Mental model: polymorphism is a universal remote. Same button (the method name), different result per device (per class). The loop doesn't care what each animal is — only that it can
speak().
Why polymorphism matters: it lets you write code that works on categories of objects instead of specific ones. Add a new class later (class Parrot(Animal)) and every existing loop, sort, and function that expects .speak() immediately works with it — zero changes.
isinstance() and issubclass()
| Function | Question | Example |
|---|---|---|
isinstance(obj, Class) |
Is this object an instance? | isinstance(dog, Dog) → True |
issubclass(Child, Parent) |
Does it inherit? | issubclass(Dog, Animal) → True |
print(isinstance(animals[0], Dog)) # True
print(isinstance(animals[0], Animal)) # True — a Dog IS an Animal
print(issubclass(Dog, Animal)) # True
Use isinstance() instead of type(x) == ... — it understands inheritance.
Why
type(x) == Dogis wrong:type(dog) == Dogis True for aDog, buttype(dog) == Animalis False even though a Dog is an Animal.isinstance(dog, Animal)correctly returns True. When you check "is this object usable as X",isinstanceis the honest answer.
The MRO — Method Resolution Order
When Python calls a method, it searches the class tree in a defined order. ClassName.mro() prints it:
print(Dog.mro())
# [<class 'Dog'>, <class 'Animal'>, <class 'object'>]
Python looks in Dog first, then Animal, then the root object. In multiple inheritance, the search goes left to right across parents.
Mental model: the child looks for a method in its own room first; if not found, it walks up the stairs to the parent's room, then the grandparent's.
Multiple inheritance example:
class A:
def greet(self):
return "A"
class B:
def greet(self):
return "B"
class C(A, B): # parents searched left to right
pass
c = C()
print(c.greet()) # 'A' — A wins
print([cls.__name__ for cls in C.mro()]) # ['C', 'A', 'B', 'object']
Multiple inheritance is powerful but easy to overuse — most real code stays with single inheritance plus composition.
Composition vs Inheritance — Has-a vs Is-a
| Relationship | Means | Use |
|---|---|---|
| Inheritance | is-a | Dog is an Animal |
| Composition | has-a | A Car has an Engine |
class Engine:
def start(self):
return "Vroom!"
class Car:
def __init__(self):
self.engine = Engine() # composition: has-a
def start(self):
return self.engine.start()
Prefer composition for flexibility — a car with a different engine doesn't need a new class hierarchy.
Common Mistakes to Avoid
- Mistake: Copy-pasting parent methods into children — Fix: override +
super(). - Mistake:
type(x) == Doginstead ofisinstance(x, Dog)— Fix: isinstance respects inheritance. - Mistake: Deep inheritance chains (5+ levels) — Fix: prefer composition ("has-a") over inheritance for flexibility.
- Mistake: Forgetting to call
super().__init__()in the child and getting uninitialized attributes — Fix: always initialize the parent. - Mistake: Overusing multiple inheritance and creating confusing MROs — Fix: keep it simple; composition usually suffices.
Professional Tips & Tricks
- Override +
super()keeps parent logic while extending it — never copy-paste parent code. - Use
isinstance()instead oftype() == ...when checking class relationships. - Prefer composition (has-a) over deep inheritance chains for flexibility.
- Print
ClassName.mro()to debug confusing method lookups. - Design for polymorphism: code against the interface (speak, area, save), not specific classes.
Key Takeaways
- Children inherit and override;
super()reaches the parent. - Polymorphism: same method name, different behavior per class.
isinstance()/issubclass()check relationships..mro()reveals Python's method search order.- Is-a → inheritance; has-a → composition.
Next up: Encapsulation, properties & magic methods.
# Inheritance + polymorphism
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "...generic sound..."
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# Polymorphism: same interface, different behavior
animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for a in animals:
print(f"{a.name}: {a.speak()}")
# Type checks
print("Rocky is a Dog:", isinstance(animals[0], Dog))
print("Dog is Animal subclass:", issubclass(Dog, Animal))
# Method resolution order
print("Dog MRO:", [c.__name__ for c in Dog.mro()])Lesson Code (Python)
# Inheritance + polymorphism
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "...generic sound..."
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# Polymorphism: same interface, different behavior
animals = [Dog("Rocky"), Cat("Whiskers"), Animal("Mystery")]
for a in animals:
print(f"{a.name}: {a.speak()}")
# Type checks
print("Rocky is a Dog:", isinstance(animals[0], Dog))
print("Dog is Animal subclass:", issubclass(Dog, Animal))
# Method resolution order
print("Dog MRO:", [c.__name__ for c in Dog.mro()])Console Output
Rocky: Woof!
Whiskers: Meow!
Mystery: ...generic sound...
Rocky is a Dog: True
Dog is Animal subclass: True
Dog MRO: ['Dog', 'Animal', 'object']Code Visualization Tips
- Draw the class tree and trace a method call walking up until it finds the first implementation.
- For polymorphism, picture a universal remote that works on every device — same button, different result.
- Use Dog.mro() to literally print the search order Python will follow.
Professional Tips & Tricks
- Override + super() keeps parent logic while extending it — never copy-paste parent code.
- Use isinstance() instead of type() == ... when checking class relationships.
- Prefer composition (has-a) over deep inheritance chains for flexibility.
Python Code Judge & Practice Arena
LeetCode StyleRun real Python 3.12 WebAssembly code directly in your browser against automated test suites.
Problem 1: Polymorphic Shapes 1
Test Your Knowledge
Instant feedbackQuick Check: Lesson 17: Inheritance, Polymorphism, `super()` & MRO
Up next · Continue learning
Encapsulation, Properties & Magic Methods
Private attributes, @property for smart access, and dunder methods that customize objects.