We use cookies to enhance your experience on the site
CodeWorlds

Inheritance - evolution of species

Welcome again, @name! 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

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())
29# "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_backbone(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_backbone())  # 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 Mammal

Multiple 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

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 for {self.gestation_days // 30} months"
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"\n🐆 LEOPARD:")
263print(f"  {leopard.describe()}")
264print(f"  {leopard.hunt()}")
265print(f"  Danger level: {leopard.calculate_danger_level()}/10")
266
267print(f"\n🐺 AFRICAN 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"\n🐘 ELEPHANT:")
274print(f"  {elephant.describe()}")
275print(f"  {elephant.graze()}")
276print(f"  Observations: {elephant.get_total_observed()} individuals")
277
278print(f"\n🦒 GIRAFFE:")
279print(f"  {giraffe.describe()}")
280print(f"  {giraffe.graze()}")
281
282print(f"\n🦅 EAGLE:")
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!" - works

3. Avoid deep hierarchies

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    pass
24# 3 levels - readable!

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()
    and
    issubclass()
  • ✅ 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! 🔒📊

Go to CodeWorlds