Python course Β· Module 3: Object-Oriented Programming
Inheritance - evolution of species
In this lesson11
Welcome again! Darwin here with a fascinating topic.
In previous lessons you learned to create classes representing species. But in nature, species are not isolated - they are part of a taxonomic hierarchy! Lions and tigers are different species, but both are felines. Felines and canines are different families, but both are mammals.
In object-oriented programming, this hierarchy is called inheritance - one of the most powerful OOP mechanisms!
What is inheritance?
Inheritance allows creating new classes based on existing ones:
- Base class (parent, superclass) - more general, e.g., "Mammal"
- Derived class (child, subclass) - more specific, e.g., "Lion"
The derived class inherits attributes and methods from the base class, and can then:
- Use inherited methods without changes
- Extend base classes (add new methods)
- Override methods - change behavior
1# Base class - general Mammal
2class Mammal:
3 def __init__(self, name):
4 self.name = name
5
6 def breathe(self):
7 return f"{self.name} breathes air"
8
9 def move(self):
10 return f"{self.name} moves"
11
12# Derived class - Lion inherits from Mammal
13class Lion(Mammal):
14 def __init__(self, name, pride_name):
15 super().__init__(name) # Call parent constructor
16 self.pride_name = pride_name # Add own attribute
17
18 def roar(self):
19 """New method - only for lions"""
20 return f"{self.name} roars: RRROAAR!"
21
22 def move(self):
23 """Override parent method"""
24 return f"{self.name} runs across the savanna"
25
26# Usage
27simba = Lion("Simba", "Pride Rock")
28
29# Inherited from Mammal
30print(simba.breathe()) # "Simba breathes air"
31
32# Overridden in Lion
33print(simba.move()) # "Simba runs across the savanna"
34
35# Own Lion method
36print(simba.roar()) # "Simba roars: RRROAAR!"
37
38# Own Lion attribute
39print(simba.pride_name) # "Pride Rock"Inheritance syntax
You write the base class name in parentheses after the new class name. The child class gets all the parent's methods right away and can add its own:
1# Basic syntax
2class ChildClass(ParentClass):
3 pass
4
5# Inheritance with extension
6class Animal:
7 def eat(self):
8 return "I eat"
9
10class Dog(Animal): # Dog inherits from Animal
11 def bark(self):
12 return "Woof woof!"
13
14dog = Dog()
15print(dog.eat()) # Inherited: "I eat"
16print(dog.bark()) # Own: "Woof woof!"The super() function
super() allows referring to the base class - use it to:
- Call the parent constructor
- Extend the parent method (not completely override it)
1class Animal:
2 def __init__(self, name, age):
3 self.name = name
4 self.age = age
5 print(f"Creating animal: {name}")
6
7 def describe(self):
8 return f"{self.name}, age: {self.age}"
9
10class Bird(Animal):
11 def __init__(self, name, age, wingspan):
12 # Call Animal constructor
13 super().__init__(name, age)
14 # Add own attribute
15 self.wingspan = wingspan
16 print(f"Wingspan: {wingspan}m")
17
18 def describe(self):
19 # Extend parent method
20 base_desc = super().describe() # Call Animal.describe()
21 return f"{base_desc}, wingspan: {self.wingspan}m"
22
23eagle = Bird("Eagle", 5, 2.3)
24# Prints:
25# Creating animal: Eagle
26# Wingspan: 2.3m
27
28print(eagle.describe()) # "Eagle, age: 5, wingspan: 2.3m"Method Overriding
A derived class can completely replace a parent method:
1class Animal:
2 def speak(self):
3 return "Animal makes a sound"
4
5class Dog(Animal):
6 def speak(self):
7 """Override completely"""
8 return "Woof woof!" # Completely new behavior
9
10class Cat(Animal):
11 def speak(self):
12 """Override completely"""
13 return "Meow!"
14
15dog = Dog()
16cat = Cat()
17
18print(dog.speak()) # "Woof woof!" - not "Animal makes a sound"
19print(cat.speak()) # "Meow!"Class hierarchy - taxonomic tree
In nature the hierarchy is deep: Kingdom β Phylum β Class β Order β Family β Genus β Species
1# 1. Kingdom Animalia
2class Animal:
3 kingdom = "Animalia"
4
5 def __init__(self, name):
6 self.name = name
7
8 def is_alive(self):
9 return True
10
11 def __str__(self):
12 return f"{self.name} ({self.__class__.__name__})"
13
14# 2. Phylum Chordata
15class Chordate(Animal):
16 phylum = "Chordata"
17
18 def has_notochord(self):
19 return True
20
21# 3. Class Mammalia
22class Mammal(Chordate):
23 class_name = "Mammalia"
24
25 def __init__(self, name, fur_color):
26 super().__init__(name)
27 self.fur_color = fur_color
28
29 def nurse_young(self):
30 return f"{self.name} nurses its young with milk"
31
32 def regulate_temperature(self):
33 return f"{self.name} maintains constant body temperature"
34
35# 4. Order Carnivora
36class Carnivore(Mammal):
37 order = "Carnivora"
38
39 def __init__(self, name, fur_color, hunting_style):
40 super().__init__(name, fur_color)
41 self.hunting_style = hunting_style
42
43 def hunt(self):
44 return f"{self.name} hunts using: {self.hunting_style}"
45
46# 5. Family Felidae
47class Feline(Carnivore):
48 family = "Felidae"
49
50 def retract_claws(self):
51 return f"{self.name} retracts its claws"
52
53 def purr(self):
54 return f"{self.name} purrs"
55
56# 6. Species Panthera leo (Lion)
57class Lion(Feline):
58 species = "Panthera leo"
59
60 def __init__(self, name, fur_color, pride_name):
61 super().__init__(name, fur_color, "pack hunting")
62 self.pride_name = pride_name
63
64 def roar(self):
65 return f"{self.name} roars loudly!"
66
67 def lead_pride(self):
68 return f"{self.name} leads the {self.pride_name} pride"
69
70# Usage - the lion has access to ALL methods in the hierarchy!
71simba = Lion("Simba", "golden", "Pride Rock")
72
73# From Animal
74print(simba.is_alive()) # True
75
76# From Chordate
77print(simba.has_notochord()) # True
78
79# From Mammal
80print(simba.nurse_young()) # "Simba nurses its young with milk"
81print(simba.regulate_temperature()) # "Simba maintains constant body temperature"
82
83# From Carnivore
84print(simba.hunt()) # "Simba hunts using: pack hunting"
85
86# From Feline
87print(simba.retract_claws()) # "Simba retracts its claws"
88print(simba.purr()) # "Simba purrs"
89
90# Own Lion methods
91print(simba.roar()) # "Simba roars loudly!"
92print(simba.lead_pride()) # "Simba leads the Pride Rock pride"
93
94# Attributes
95print(simba.kingdom) # "Animalia"
96print(simba.species) # "Panthera leo"
97print(simba.fur_color) # "golden"Checking class relationships
Python offers functions to check the hierarchy:
1class Animal:
2 pass
3
4class Mammal(Animal):
5 pass
6
7class Lion(Mammal):
8 pass
9
10simba = Lion()
11
12# isinstance() - check if object is an instance of a class
13print(isinstance(simba, Lion)) # True
14print(isinstance(simba, Mammal)) # True - Lion inherits from Mammal
15print(isinstance(simba, Animal)) # True - Lion inherits from Animal (through Mammal)
16print(isinstance(simba, str)) # False
17
18# issubclass() - check if a class inherits from another class
19print(issubclass(Lion, Mammal)) # True
20print(issubclass(Lion, Animal)) # True
21print(issubclass(Mammal, Lion)) # False
22print(issubclass(Lion, Lion)) # True - a class is a subclass of itself
23
24# type() - return the exact type
25print(type(simba)) # <class '__main__.Lion'>
26print(type(simba) == Lion) # True
27print(type(simba) == Mammal) # False - simba is a Lion, not a MammalMultiple Inheritance
Python allows inheriting from multiple classes at once - a class can have multiple parents!
1class Swimmer:
2 def swim(self):
3 return f"{self.name} swims"
4
5class Flyer:
6 def fly(self):
7 return f"{self.name} flies"
8
9class Walker:
10 def walk(self):
11 return f"{self.name} walks"
12
13# Duck inherits from three classes!
14class Duck(Swimmer, Flyer, Walker):
15 def __init__(self, name):
16 self.name = name
17
18 def quack(self):
19 return f"{self.name}: Quack quack!"
20
21donald = Duck("Donald")
22
23# Has access to methods from all base classes
24print(donald.swim()) # "Donald swims"
25print(donald.fly()) # "Donald flies"
26print(donald.walk()) # "Donald walks"
27print(donald.quack()) # "Donald: Quack quack!"MRO - Method Resolution Order
When a class inherits from multiple parents, Python uses MRO (Method Resolution Order) to determine the method search order:
1class A:
2 def method(self):
3 return "A"
4
5class B(A):
6 def method(self):
7 return "B"
8
9class C(A):
10 def method(self):
11 return "C"
12
13class D(B, C): # Inherits from B and C
14 pass
15
16d = D()
17print(d.method()) # "B" - Python searches: D β B β C β A
18
19# Check MRO
20print(D.__mro__)
21# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
22# <class '__main__.A'>, <class 'object'>)
23
24# Or more readable:
25print(D.mro())Rule: Python searches classes from left to right, from most specific to most general.
Safari example - species hierarchy with observations
Now the whole safari hierarchy: a shared Species class, below it mammals and birds, then carnivores and herbivores, and at the very bottom felines and canines. Each level adds its own attributes and extends describe() with super().
1class Species:
2 """
3 Base class for all species in the Safari catalog
4 """
5
6 kingdom = "Animalia"
7 total_species_count = 0
8
9 def __init__(self, scientific_name, common_name, habitat):
10 self.scientific_name = scientific_name
11 self.common_name = common_name
12 self.habitat = habitat
13 self.observations = []
14
15 Species.total_species_count += 1
16
17 def add_observation(self, date, location, count, notes=""):
18 """Add a field observation"""
19 self.observations.append({
20 "date": date,
21 "location": location,
22 "count": count,
23 "notes": notes
24 })
25
26 def get_total_observed(self):
27 """Total number of observed individuals"""
28 return sum(obs["count"] for obs in self.observations)
29
30 def describe(self):
31 """Basic species description"""
32 return f"{self.common_name} ({self.scientific_name})"
33
34 def __str__(self):
35 return self.describe()
36
37class Mammal(Species):
38 """
39 Class representing mammals - extends Species
40 """
41
42 class_name = "Mammalia"
43
44 def __init__(self, scientific_name, common_name, habitat, fur_color, gestation_days):
45 # Call Species constructor
46 super().__init__(scientific_name, common_name, habitat)
47 # Add mammal-specific attributes
48 self.fur_color = fur_color
49 self.gestation_days = gestation_days
50
51 def nurse_young(self):
52 """Mammals nurse their young with milk"""
53 return f"{self.common_name} nurses its young (gestation lasts {self.gestation_days} days)"
54
55 def describe(self):
56 """Extend description with mammal information"""
57 base = super().describe() # Call Species.describe()
58 return f"{base} | Mammal | Fur: {self.fur_color}"
59
60class Bird(Species):
61 """
62 Class representing birds
63 """
64
65 class_name = "Aves"
66
67 def __init__(self, scientific_name, common_name, habitat, wingspan_m, can_fly=True):
68 super().__init__(scientific_name, common_name, habitat)
69 self.wingspan_m = wingspan_m
70 self.can_fly = can_fly
71
72 def lay_eggs(self):
73 return f"{self.common_name} lays eggs"
74
75 def describe(self):
76 base = super().describe()
77 flight_status = "flying" if self.can_fly else "flightless"
78 return f"{base} | {flight_status} Bird | Wingspan: {self.wingspan_m}m"
79
80class Carnivore(Mammal):
81 """
82 Predators - inherit from Mammal
83 """
84
85 order = "Carnivora"
86
87 def __init__(self, scientific_name, common_name, habitat, fur_color,
88 gestation_days, hunting_style, pack_hunter=False):
89 super().__init__(scientific_name, common_name, habitat, fur_color, gestation_days)
90 self.hunting_style = hunting_style
91 self.pack_hunter = pack_hunter
92
93 def hunt(self):
94 style = "in a pack" if self.pack_hunter else "alone"
95 return f"{self.common_name} hunts {style} using: {self.hunting_style}"
96
97 def calculate_danger_level(self):
98 """Calculate danger level (1-10)"""
99 base_danger = 5
100 if self.pack_hunter:
101 base_danger += 3
102 if "ambush" in self.hunting_style:
103 base_danger += 2
104 return min(10, base_danger)
105
106 def describe(self):
107 base = super().describe()
108 danger = self.calculate_danger_level()
109 return f"{base} | Predator | Danger: {danger}/10"
110
111class Herbivore(Mammal):
112 """
113 Herbivores - inherit from Mammal
114 """
115
116 def __init__(self, scientific_name, common_name, habitat, fur_color,
117 gestation_days, diet_type):
118 super().__init__(scientific_name, common_name, habitat, fur_color, gestation_days)
119 self.diet_type = diet_type # "leaves", "grass", "fruit"
120
121 def graze(self):
122 return f"{self.common_name} eats {self.diet_type}"
123
124 def describe(self):
125 base = super().describe()
126 return f"{base} | Herbivore | Diet: {self.diet_type}"
127
128class Feline(Carnivore):
129 """
130 Felines - inherit from Carnivore
131 """
132
133 family = "Felidae"
134
135 def __init__(self, scientific_name, common_name, habitat, fur_color,
136 gestation_days, has_mane=False, pride_size=1):
137 super().__init__(
138 scientific_name, common_name, habitat, fur_color,
139 gestation_days, "ambush and chase", pack_hunter=(pride_size > 1)
140 )
141 self.has_mane = has_mane
142 self.pride_size = pride_size
143
144 def retract_claws(self):
145 return f"{self.common_name} retracts its sharp claws"
146
147 def stalk_prey(self):
148 return f"{self.common_name} stalks its prey silently"
149
150 def describe(self):
151 base = super().describe()
152 social = f"social ({self.pride_size})" if self.pride_size > 1 else "solitary"
153 return f"{base} | Feline | {social}"
154
155class Canine(Carnivore):
156 """
157 Canines - inherit from Carnivore
158 """
159
160 family = "Canidae"
161
162 def __init__(self, scientific_name, common_name, habitat, fur_color,
163 gestation_days, pack_size=1):
164 super().__init__(
165 scientific_name, common_name, habitat, fur_color,
166 gestation_days, "endurance pursuit", pack_hunter=(pack_size > 1)
167 )
168 self.pack_size = pack_size
169
170 def howl(self):
171 return f"{self.common_name} howls to the pack"
172
173 def track_scent(self):
174 return f"{self.common_name} tracks prey by scent"
175
176 def describe(self):
177 base = super().describe()
178 social = f"pack ({self.pack_size})" if self.pack_size > 1 else "solitary"
179 return f"{base} | Canine | {social}"
180
181# === DEMONSTRATION - Creating Safari hierarchy ===
182
183print("=== SAFARI SPECIES CATALOG - HIERARCHY ===\n")
184
185# Felines
186lion = Feline(
187 scientific_name="Panthera leo",
188 common_name="Lion",
189 habitat="savanna",
190 fur_color="golden",
191 gestation_days=110,
192 has_mane=True,
193 pride_size=15
194)
195
196leopard = Feline(
197 scientific_name="Panthera pardus",
198 common_name="Leopard",
199 habitat="savanna and forests",
200 fur_color="yellow with black spots",
201 gestation_days=90,
202 has_mane=False,
203 pride_size=1 # Solitary
204)
205
206# Canines
207wild_dog = Canine(
208 scientific_name="Lycaon pictus",
209 common_name="African Wild Dog",
210 habitat="savanna",
211 fur_color="mottled",
212 gestation_days=70,
213 pack_size=20
214)
215
216# Herbivores
217elephant = Herbivore(
218 scientific_name="Loxodonta africana",
219 common_name="African Elephant",
220 habitat="savanna",
221 fur_color="gray",
222 gestation_days=645, # Longest gestation!
223 diet_type="leaves, bark, grass"
224)
225
226giraffe = Herbivore(
227 scientific_name="Giraffa camelopardalis",
228 common_name="Giraffe",
229 habitat="savanna",
230 fur_color="yellow with brown patches",
231 gestation_days=440,
232 diet_type="acacia leaves"
233)
234
235# Birds
236eagle = Bird(
237 scientific_name="Aquila rapax",
238 common_name="Tawny Eagle",
239 habitat="savanna",
240 wingspan_m=2.1,
241 can_fly=True
242)
243
244# Add observations
245lion.add_observation("2024-01-15", "Serengeti", 12, "Pride hunting wildebeest")
246lion.add_observation("2024-01-20", "Masai Mara", 8, "Family with cubs")
247leopard.add_observation("2024-01-16", "Rocky area", 1, "Solitary male")
248wild_dog.add_observation("2024-01-18", "Savuti", 18, "Pack hunting")
249elephant.add_observation("2024-01-17", "Amboseli", 45, "Large herd at waterhole")
250giraffe.add_observation("2024-01-19", "Tarangire", 23, "Giraffes feeding on acacias")
251eagle.add_observation("2024-01-21", "Sky over savanna", 3, "Pair with juvenile")
252
253# Display descriptions - each uses its own version of describe()
254print("LION:")
255print(f" {lion.describe()}")
256print(f" {lion.hunt()}")
257print(f" {lion.stalk_prey()}")
258print(f" {lion.retract_claws()}")
259print(f" {lion.nurse_young()}")
260print(f" Observations: {lion.get_total_observed()} individuals")
261
262print(f"\nLEOPARD:")
263print(f" {leopard.describe()}")
264print(f" {leopard.hunt()}")
265print(f" Danger level: {leopard.calculate_danger_level()}/10")
266
267print(f"\nAFRICAN WILD DOG:")
268print(f" {wild_dog.describe()}")
269print(f" {wild_dog.hunt()}")
270print(f" {wild_dog.howl()}")
271print(f" {wild_dog.track_scent()}")
272
273print(f"\nELEPHANT:")
274print(f" {elephant.describe()}")
275print(f" {elephant.graze()}")
276print(f" Observations: {elephant.get_total_observed()} individuals")
277
278print(f"\nGIRAFFE:")
279print(f" {giraffe.describe()}")
280print(f" {giraffe.graze()}")
281
282print(f"\nEAGLE:")
283print(f" {eagle.describe()}")
284print(f" {eagle.lay_eggs()}")
285
286# Check hierarchy
287print(f"\n=== CLASS HIERARCHY ===")
288print(f"Lion inherits from Feline? {isinstance(lion, Feline)}")
289print(f"Lion inherits from Carnivore? {isinstance(lion, Carnivore)}")
290print(f"Lion inherits from Mammal? {isinstance(lion, Mammal)}")
291print(f"Lion inherits from Species? {isinstance(lion, Species)}")
292print(f"Lion inherits from Bird? {isinstance(lion, Bird)}")
293
294print(f"\nMRO Feline: {[cls.__name__ for cls in Feline.mro()]}")
295# ['Feline', 'Carnivore', 'Mammal', 'Species', 'object']
296
297print(f"\n=== STATISTICS ===")
298print(f"Total species in catalog: {Species.total_species_count}")
299
300# Polymorphism - each species has its own implementation of describe()
301print(f"\nAll species:")
302all_animals = [lion, leopard, wild_dog, elephant, giraffe, eagle]
303for animal in all_animals:
304 print(f" - {animal.describe()}")Practical inheritance rules
1. When to use inheritance?
Use inheritance when:
- "is-a" relationship: Lion is a Mammal
- You want to reuse code from the base class
- You're creating a type hierarchy
DON'T use inheritance when:
- "has-a" relationship: Car has an Engine (use composition!)
- Classes don't share a common nature
- You just want to borrow a few methods
1# Good - "is-a" relationship
2class Animal:
3 pass
4
5class Dog(Animal): # Dog IS an animal
6 pass
7
8# Bad - "has-a" relationship
9class Engine:
10 def start(self):
11 return "Engine started"
12
13class Car(Engine): # NO! Car IS NOT an engine!
14 pass
15
16# Good - composition
17class Car:
18 def __init__(self):
19 self.engine = Engine() # Car HAS an engine
20
21 def start(self):
22 return self.engine.start()2. Liskov Substitution Principle (LSP)
An object of a derived class should be able to replace an object of the base class without changing program correctness.
1def make_sound(animal):
2 """Function accepts any Animal"""
3 print(animal.speak())
4
5class Animal:
6 def speak(self):
7 return "..."
8
9class Dog(Animal):
10 def speak(self):
11 return "Woof!"
12
13class Cat(Animal):
14 def speak(self):
15 return "Meow!"
16
17# LSP - Dog and Cat can replace Animal
18make_sound(Dog()) # "Woof!" - works
19make_sound(Cat()) # "Meow!" - works3. Avoid deep hierarchies
Every level of inheritance is one more class you have to read to understand an object. Compare six empty levels with three that mean something:
1# Bad - hierarchy too deep
2class A:
3 pass
4class B(A):
5 pass
6class C(B):
7 pass
8class D(C):
9 pass
10class E(D):
11 pass
12class F(E): # 6 levels!
13 pass
14
15# Good - flat, understandable hierarchy
16class Animal:
17 pass
18
19class Mammal(Animal):
20 pass
21
22class Lion(Mammal):
23 passThree levels, Animal β Mammal β Lion, are readable at a glance. When a hierarchy grows beyond three or four levels, check whether some behavior would fit better in composition.
Summary
In this lesson you learned:
- What inheritance is and why it's powerful
- How to create derived classes with
class Child(Parent) - Using
super()to call parent methods - Overriding and extending methods
- Building class hierarchies (taxonomic tree)
- Checking relationships with
isinstance()andissubclass() - Multiple inheritance and MRO
- Practical rules: when to use inheritance and when not to
Checkpoint
Before moving on:
- You understand the difference between base and derived classes
- You can use
super()in a constructor - You know how to override a parent method
- You understand hierarchy and multi-level inheritance
- You know the difference between "is-a" (inheritance) and "has-a" (composition)
Safari Analogy: A class hierarchy is an evolutionary tree - each descendant inherits traits from ancestors but develops its own unique adaptations!
In the next lesson Darwin will teach you encapsulation - how to protect sensitive species data from unauthorized access!
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you define a Cat class that inherits from Animal?
2. What is super().__init__() used for?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
Arrange the elements in the correct order:
- Code editor
Create a Tiger class inheriting from Animal(name, species): in __init__, call super().__init__(name, species) and add a stripes attribute. Create one tiger
- Code editor
The Animal class has a speak() method. Override it in the Tiger(Animal) class so that it displays 'Rrrrr!'. Create a tiger and call speak()
- Horizontal ordering
Arrange the header of a Tiger class that inherits from Animal:
- Click in order
Click the parts of a call to the parent class constructor with the argument name: