Python course Β· Module 3: Object-Oriented Programming

Classes as species in nature

10 min read
In this lesson9

Welcome to Module 3! Darwin here with a new programming paradigm.

So far you've learned procedural programming - you wrote functions that process data. Now it's time for Object-Oriented Programming (OOP) - a way of organizing code around objects that combine data and behaviors.

Imagine biological classification. Biologists don't describe every animal from scratch. Instead, they create species with shared characteristics:

  • All lions (Panthera leo) have four legs, are carnivorous, live in prides
  • All ball pythons (Python regius) are snakes, swallow prey whole, hunt at night

In OOP we do the same thing - we create classes as "templates" for objects!

What is Object-Oriented Programming?

OOP is a programming paradigm based on the concept of objects that contain:

  • Data (attributes, properties) - describe the object's state
  • Behaviors (methods) - describe what the object can do

Key OOP concepts:

  1. Class - a template, a recipe, a biological species
  2. Object - a specific instance of a class, a specific animal
  3. Attributes - the object's data (animal characteristics)
  4. Methods - functions associated with the object (animal behaviors)
1# Simple example - biological classification
2
3class Species:
4    """Class representing a species"""
5
6    def __init__(self, name, habitat, diet):
7        """Constructor - initializes the object"""
8        self.name = name
9        self.habitat = habitat
10        self.diet = diet
11
12    def describe(self):
13        """Method - describe the species"""
14        return f"{self.name} - habitat: {self.habitat}, diet: {self.diet}"
15
16# Creating objects (instances of the class)
17lion = Species("Lion", "savanna", "meat")
18python = Species("Python", "jungle", "rodents")
19
20# Using objects
21print(lion.describe())   # "Lion - habitat: savanna, diet: meat"
22print(python.describe()) # "Python - habitat: jungle, diet: rodents"

This example has two new elements. __init__ is the constructor: Python calls it automatically when an object is created, e.g. Species("Lion", "savanna", "meat"). self is a reference to the current object, this particular animal: self.name = name stores the name in it. We will look at both more closely in the next lesson.

Why OOP?

Without OOP (procedural)

Let's start with the style you already know: every trait of an animal is a separate variable, and the function receives all of them as arguments.

1# Data and functions are separate
2lion_name = "Lion"
3lion_habitat = "savanna"
4lion_diet = "meat"
5
6python_name = "Python"
7python_habitat = "jungle"
8python_diet = "rodents"
9
10def describe_animal(name, habitat, diet):
11    return f"{name} - habitat: {habitat}, diet: {diet}"
12
13print(describe_animal(lion_name, lion_habitat, lion_diet))
14print(describe_animal(python_name, python_habitat, python_diet))

With two animals this still works, but every new one means three more variables to track. With twenty species it is easy to mix up the names and pass the function the lion's habitat together with the python's diet.

With OOP

Now the same two animals written as objects: the Animal class keeps the data and the describe() method in one place.

1# Data and functions are together in objects
2class Animal:
3    def __init__(self, name, habitat, diet):
4        self.name = name
5        self.habitat = habitat
6        self.diet = diet
7
8    def describe(self):
9        return f"{self.name} - habitat: {self.habitat}, diet: {self.diet}"
10
11lion = Animal("Lion", "savanna", "meat")
12python = Animal("Python", "jungle", "rodents")
13
14print(lion.describe())
15print(python.describe())

The output is the same, but the code is better organized: data and behavior live together, and each new animal is one Animal(...) line instead of three new variables.

4 Pillars of OOP

1. Encapsulation

Grouping data and methods in a single object + hiding implementation details.

1class BankAccount:
2    def __init__(self, balance):
3        self.__balance = balance  # Private attribute (__)
4
5    def deposit(self, amount):
6        """Public method"""
7        if amount > 0:
8            self.__balance += amount
9
10    def get_balance(self):
11        """Public access to private attribute"""
12        return self.__balance
13
14account = BankAccount(1000)
15account.deposit(500)
16print(account.get_balance())  # 1500

The balance changes only through the deposit() method, which accepts only positive amounts. Trying to read account.__balance from outside the class raises an AttributeError, because an attribute with two underscores is private.

2. Abstraction

Hiding complexity, showing only the necessary interface.

1class Car:
2    def start_engine(self):
3        """Simple interface"""
4        self.__check_fuel()
5        self.__ignite_spark_plugs()
6        self.__start_motor()
7        print("Engine started!")
8
9    def __check_fuel(self):
10        """Hidden implementation"""
11        pass
12
13    def __ignite_spark_plugs(self):
14        """Hidden implementation"""
15        pass
16
17    def __start_motor(self):
18        """Hidden implementation"""
19        pass
20
21car = Car()
22car.start_engine()  # Simple interface - no need to know the details!

3. Inheritance

Creating new classes based on existing ones - a taxonomic hierarchy!

1class Animal:
2    """Base class (parent, superclass)"""
3    def __init__(self, name):
4        self.name = name
5
6    def eat(self):
7        return f"{self.name} eats"
8
9class Mammal(Animal):
10    """Derived class (child, subclass)"""
11    def __init__(self, name, fur_color):
12        super().__init__(name)  # Call base class constructor
13        self.fur_color = fur_color
14
15    def nurse_young(self):
16        return f"{self.name} nurses its young with milk"
17
18lion = Mammal("Lion", "golden")
19print(lion.eat())          # Inherited from Animal
20print(lion.nurse_young())  # Own Mammal method

4. Polymorphism

Different objects can respond to the same method in different ways.

1class Dog:
2    def speak(self):
3        return "Woof woof!"
4
5class Cat:
6    def speak(self):
7        return "Meow!"
8
9class Cow:
10    def speak(self):
11        return "Moo!"
12
13# Polymorphism - same method, different behaviors
14animals = [Dog(), Cat(), Cow()]
15for animal in animals:
16    print(animal.speak())  # Each one "speaks" differently!

Safari example - Species classification

Let's combine these elements in one class: a species has a scientific name, a habitat and a list of observations, and its methods count individuals and assess whether the species is threatened.

1class Species:
2    """
3    Class representing a species in the Safari expedition
4
5    Analogy: A biological species with shared characteristics
6    """
7
8    def __init__(self, scientific_name, common_name, habitat, dangerous=False):
9        """
10        Constructor - initializes a new species
11
12        Args:
13            scientific_name: Scientific name (e.g., "Panthera leo")
14            common_name: Common name (e.g., "Lion")
15            habitat: Natural environment
16            dangerous: Whether dangerous to humans
17        """
18        self.scientific_name = scientific_name
19        self.common_name = common_name
20        self.habitat = habitat
21        self.dangerous = dangerous
22        self.observations = []  # List of observations
23
24    def add_observation(self, location, count, notes=""):
25        """Add a species observation"""
26        observation = {
27            "location": location,
28            "count": count,
29            "notes": notes
30        }
31        self.observations.append(observation)
32        print(f"Added observation: {count}x {self.common_name} in {location}")
33
34    def get_total_observed(self):
35        """Return the total number of observed individuals"""
36        return sum(obs["count"] for obs in self.observations)
37
38    def is_threatened(self):
39        """Check if the species is threatened (fewer than 10 observations)"""
40        return self.get_total_observed() < 10
41
42    def get_status(self):
43        """Return the species status"""
44        total = self.get_total_observed()
45        if total == 0:
46            return "Not observed"
47        elif self.is_threatened():
48            return f"Threatened ({total} individuals)"
49        else:
50            return f"Stable ({total} individuals)"
51
52    def describe(self):
53        """Full species description"""
54        danger_status = "DANGEROUS" if self.dangerous else "Safe"
55        return f"""
56╔═══════════════════════════════════════════════╗
57  {self.common_name} ({self.scientific_name})
58  Habitat: {self.habitat}
59  Status: {danger_status}
60  Observations: {len(self.observations)}
61  Individuals: {self.get_total_observed()}
62  Condition: {self.get_status()}
63β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
64        """.strip()
65
66# Usage
67lion = Species(
68    scientific_name="Panthera leo",
69    common_name="Lion",
70    habitat="savanna",
71    dangerous=True
72)
73
74# Add observations
75lion.add_observation("Northern Savanna", 5, "Hunting pride")
76lion.add_observation("Rift Valley", 3, "Two adults with cubs")
77lion.add_observation("Serengeti Park", 8)
78
79# Display information
80print(lion.describe())
81print(f"\nTotal observed: {lion.get_total_observed()} lions")
82print(f"Is threatened? {'Yes' if lion.is_threatened() else 'No'}")

Comparison: Functions vs Classes

Procedural approach (functions and dictionaries)

First, a species as a plain dictionary and three separate functions that create, modify and summarize that dictionary.

1def create_species(name, habitat):
2    """Returns a dictionary representing a species"""
3    return {
4        "name": name,
5        "habitat": habitat,
6        "observations": []
7    }
8
9def add_observation(species_dict, location, count):
10    """Modifies the dictionary"""
11    species_dict["observations"].append({"location": location, "count": count})
12
13def get_total(species_dict):
14    """Calculates from the dictionary"""
15    return sum(obs["count"] for obs in species_dict["observations"])
16
17# Usage - functions and data are separate
18lion = create_species("Lion", "savanna")
19add_observation(lion, "North", 5)
20print(get_total(lion))

Object-oriented approach

The same logic as a class: the dictionary keys become attributes of the object, and the functions become its methods.

1class Species:
2    def __init__(self, name, habitat):
3        self.name = name
4        self.habitat = habitat
5        self.observations = []
6
7    def add_observation(self, location, count):
8        """Data and logic together!"""
9        self.observations.append({"location": location, "count": count})
10
11    def get_total(self):
12        return sum(obs["count"] for obs in self.observations)
13
14# Usage - data and behaviors together
15lion = Species("Lion", "savanna")
16lion.add_observation("North", 5)
17print(lion.get_total())

Advantages of OOP:

  • Data and methods are together (logical grouping)
  • Easier state management
  • Ability to inherit and reuse code
  • Better design for complex systems
  • Encapsulation protects data

When to use OOP?

Use OOP when:

  • Modeling real-world entities (animals, vehicles, users)
  • You need many similar objects with different data
  • You want to hide implementation details (encapsulation)
  • You need hierarchies and inheritance
  • Designing an API or library

Use functions/procedures when:

  • Simple, linear script
  • Stateless data processing
  • Utility functions (helper tools)
  • You don't need multiple instances

Practical example - Expedition Manager

Finally, a bigger class that runs a whole expedition: it records the team members and the discoveries of each day, and prints a report at the end.

1class Expedition:
2    """
3    Class managing a Safari expedition
4
5    Combines data (location, team, discoveries) and behaviors (adding, reporting)
6    """
7
8    def __init__(self, name, leader, start_date):
9        self.name = name
10        self.leader = leader
11        self.start_date = start_date
12        self.team_members = []
13        self.discovered_species = []
14        self.days_elapsed = 0
15
16    def add_team_member(self, member_name, role):
17        """Add a team member"""
18        member = {"name": member_name, "role": role}
19        self.team_members.append(member)
20        print(f"{member_name} ({role}) joined the expedition")
21
22    def discover_species(self, species_name, location, count=1):
23        """Record a species discovery"""
24        discovery = {
25            "species": species_name,
26            "location": location,
27            "count": count,
28            "day": self.days_elapsed
29        }
30        self.discovered_species.append(discovery)
31        print(f"Day {self.days_elapsed}: Discovered {count}x {species_name} in {location}")
32
33    def advance_day(self):
34        """Next expedition day"""
35        self.days_elapsed += 1
36        print(f"\nDay {self.days_elapsed} of expedition '{self.name}'")
37
38    def get_statistics(self):
39        """Expedition statistics"""
40        unique_species = len(set(d["species"] for d in self.discovered_species))
41        total_animals = sum(d["count"] for d in self.discovered_species)
42
43        return {
44            "expedition": self.name,
45            "leader": self.leader,
46            "days": self.days_elapsed,
47            "team": len(self.team_members),
48            "unique_species": unique_species,
49            "total_individuals": total_animals
50        }
51
52    def generate_report(self):
53        """Generate expedition report"""
54        stats = self.get_statistics()
55
56        report = f"""
57╔══════════════════════════════════════════════════════════╗
58  EXPEDITION REPORT: {self.name}
59╠══════════════════════════════════════════════════════════╣
60  Leader: {self.leader}
61  Start date: {self.start_date}
62  Days in the field: {stats['days']}
63  Team members: {stats['team']}
64╠══════════════════════════════════════════════════════════╣
65  DISCOVERIES:
66  - Unique species: {stats['unique_species']}
67  - Total individuals: {stats['total_individuals']}
68╠══════════════════════════════════════════════════════════╣
69  TEAM:
70        """
71
72        for member in self.team_members:
73            report += f"\n  - {member['name']} ({member['role']})"
74
75        report += "\nβ•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"
76        return report
77
78# Expedition simulation
79expedition = Expedition("Safari 2024", "Dr. Jane Wilson", "2024-06-01")
80
81# Add team
82expedition.add_team_member("Darwin Brown", "Biologist")
83expedition.add_team_member("Alex Chen", "Photographer")
84expedition.add_team_member("Maya Patel", "Guide")
85
86# Expedition days
87expedition.advance_day()
88expedition.discover_species("Panthera leo", "Northern Savanna", 5)
89expedition.discover_species("Loxodonta africana", "Elephant Valley", 12)
90
91expedition.advance_day()
92expedition.discover_species("Python regius", "Jungle", 2)
93expedition.discover_species("Panthera leo", "Southern Savanna", 3)
94
95expedition.advance_day()
96expedition.discover_species("Gorilla gorilla", "Cloud Forest", 8)
97
98# Final report
99print("\n" + expedition.generate_report())

Summary

In this lesson you learned:

  • What Object-Oriented Programming (OOP) is
  • The difference between procedural and object-oriented approaches
  • The 4 pillars of OOP: encapsulation, abstraction, inheritance, polymorphism
  • Why OOP is powerful for code organization
  • When to use OOP and when to use functions
  • Basic concepts: class, object, attribute, method
  • Practical Safari examples

Checkpoint

Before moving on:

  • You understand the difference between a class and an object
  • You know the 4 pillars of OOP (encapsulation, abstraction, inheritance, polymorphism)
  • You understand why we group data and behaviors together
  • You see the analogy between classes and biological species
  • You know when to use OOP and when to use functions

Safari Analogy: A class is a species (Panthera leo), an object is a specific animal (a specific lion named Simba)!

In the next lesson Darwin will teach you how to create your own classes and objects - you'll build a complete species classification system!

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. 1. A class in Python is:

  2. 2. The __init__ method is used for:

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Arrange the steps for creating a class and object:

  • Code editor

    Create an Animal class with an __init__ method that accepts name and species and stores them in self.name and self.species

  • Code editor

    Define an Animal class with __init__(self, name, species) and create an instance of it: tiger = Animal('Tiger', 'Panthera tigris')

  • Horizontal ordering

    Arrange the header of the Animal class definition:

  • Click in order

    Click the parts of the header of a constructor that takes a name parameter:

  • Vertical ordering

    Arrange the lines of an Animal class with a constructor (first method) and a describe() method:

Useful articles