Inheritance & Polymorphism
Lesson 2 of 5
2Inheritance & Polymorphism
The big pictureHow one class inherits another's behaviour and overrides only what it needs, and why “is it a kind of?” decides whether inheritance belongs at all.
What the picture shows: Circle and Square inherit from Shape. The loop calls s.area() on every shape with the same words, and each class answers with its own sum.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
Inheritance is like a daughter taking her mother's recipe and changing only the spices. She does not rewrite it; she says "like Mum's, but with more pepper". Polymorphism is telling every animal "speak" and the dog barks while the cat meows: the same request, each answering its own way.
class Dog(Animal): inherits attributes and methods, overrides what it needs, and calls the parent with super(). Polymorphism means code works with any object that has the needed method. Inheritance fits only an is-a relation; otherwise composition is better.
Before You Start
🏛️The classes lesson
You write a class with __init__ and a method.
👨👧Family likeness
A child resembles a parent in some ways and differs in others; that is the idea of inheritance.
The Core Idea
Inheritance means a new class takes everything from an old one and changes only what it needs. Three examples show taking, changing and adding.
1A class that inherits from a class
You write the parent's name in brackets after the new class name. Cat defines no __init__, yet it got a name, because it inherited it from Animal.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Cat(Animal):
def speak(self):
return "meow"
pet = Cat("Mishmish")
print(pet.name)
print(pet.speak())
But it defined speak afresh, so its own version was used, not the parent's. That is called overriding.
2Same call, different behaviour
Three objects from three classes, and one loop calling speak on each. Every object answered its own way without the loop asking what type it was.
class Animal:
def speak(self):
return "..."
class Cat(Animal):
def speak(self):
return "meow"
class Dog(Animal):
def speak(self):
return "woof"
for animal in [Cat(), Dog(), Animal()]:
print(animal.speak())
That is polymorphism, and its value is that code using the objects does not need to know their classes in detail.
3Add without rewriting
super().__init__(name) calls the parent's __init__ to store the name as before, then you add what belongs to the child. So the parent's code is never repeated.
class Animal:
def __init__(self, name):
self.name = name
class Cat(Animal):
def __init__(self, name, indoor):
super().__init__(name)
self.indoor = indoor
c = Cat("Lulu", True)
print(c.name, c.indoor)
print(isinstance(c, Animal), isinstance(c, Cat))
isinstance confirms the cat is both an animal and a cat. A child is always a kind of its parent, never the other way round.
__init__ in the child and forgetting to call super().__init__. The parent's attributes are then never created, and you get AttributeError on the first attempt to read self.name.How It Works, Step by Step: How Python finds the right method
You call s.area() on some object.
Python looks for area in the object's own class first.
If it is not there it climbs to the parent, then the grandparent.
The first definition it finds is the one that runs, which is why the child's wins.
Going deeper
Inheritance lets a class reuse and extend another class's behavior. Polymorphism lets objects from different classes respond to the same method call, each in its own way.
Diagram 6, Developer and Manager both inherit from Employee, but each "overrides" calculate_bonus() with its own logic.
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def calculate_bonus(self):
return self.base_salary * 0.05 # generic default behavior
class Developer(Employee):
def calculate_bonus(self): # Override: replaces the parent's behavior
return self.base_salary * 0.10
class Manager(Employee):
def calculate_bonus(self):
return self.base_salary * 0.15
team = [Developer("Khalid", 8000), Manager("Mona", 12000)]
for member in team:
# same call calculate_bonus() but each object responds its own way -> polymorphism
print(f"{member.name}: {member.calculate_bonus()}")
print(isinstance(team[0], Employee)) # True — a Developer is also an Employee
print(issubclass(Manager, Employee)) # True — a class-to-class relationship
| Function | Checks whether | Example |
|---|---|---|
isinstance(obj, Cls) | This object is an instance of this class (or one of its children) | isinstance(dev, Employee) → True |
issubclass(A, B) | This class inherits from that class | issubclass(Developer, Employee) → True |
super().__init__(...) in a child class that needs extra initialization on top of the parent's, this forces you to rewrite everything from scratch instead of benefiting from the parent class's work.
Check your understanding: if you add a class Intern(Employee) without defining calculate_bonus() yourself, what happens when it's called?
The calculate_bonus() definition inherited directly from Employee is used automatically (5%), because inheritance gives you any method you didn't explicitly override in the child class.
Worked example
Employee with a fixed salary and want a Manager who gets the same plus a bonus. The answer: class Manager(Employee) with def pay(self): return super().pay() + self.bonus. super() reuses the parent's sum instead of copying it.Myth vs Reality
| Myth | Reality |
|---|---|
| Inheritance is the only way to reuse code. | Composition is often better: a class that holds an object of another class. Ask "is it a kind of?" before you inherit. |
| Overriding a method deletes the parent's. | The parent's method is still there, and the child can call it with super() and add to it. |
| Polymorphism is interview theory only. | You use it daily: len() works on text, lists and dictionaries, and each type answers its own way. |
Try it yourself
class Dog(Animal) does not define speak. What happens on Dog().speak()?
When Python does not find the method on the class itself it looks in the parent, then the parent's parent, and so on. That is inheritance in practice: whatever you did not change stays as inherited.
Quick Review Flashcards
Check Your Understanding
What does super().__init__(name) do?
It calls the parent's set-up to prepare its part, then the child finishes its own.
A Dog(Animal) class has no eat. What happens on dog.eat()?
Animal's eat runs, because the lookup climbs to the parent.
How do you tell whether an object is of a type or one of its children?
With isinstance(obj, Animal), which also says True for children.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Done with this lesson?
Sign in to save your progress.