Cours Python · Module 3: Programmation orientée objet

Créer tes propres espèces

13 min de lecture
Dans cette leçon11

Bon retour ! Darwin ici, prêt à approfondir la POO.

Dans la leçon précédente, tu as découvert le concept de classes et d'objets. Il est maintenant temps d'apprendre comment créer tes propres classes à partir de zéro - exactement comme les biologistes classent de nouvelles espèces sur le terrain !

Anatomie d'une classe en Python

Une classe se compose de plusieurs éléments clés :

1class Animal:  # 1. Nom de la classe (PascalCase)
2    """Documentation de la classe - description de ce qu'elle représente"""  # 2. Docstring
3
4    species_count = 0  # 3. Attribut de classe (partagé)
5
6    def __init__(self, name, age):  # 4. Constructeur
7        """Initialise un nouvel objet"""
8        self.name = name  # 5. Attributs d'instance
9        self.age = age
10        Animal.species_count += 1  # Incrémente le compteur
11
12    def make_sound(self):  # 6. Méthode d'instance
13        """L'animal fait un bruit"""
14        return f"{self.name} fait un bruit !"
15
16    @classmethod  # 7. Méthode de classe
17    def get_species_count(cls):
18        """Renvoie le nombre d'animaux créés"""
19        return cls.species_count
20
21    @staticmethod  # 8. Méthode statique
22    def is_adult(age):
23        """Vérifie si adulte (pas d'accès à self/cls)"""
24        return age >= 2
25
26# Création d'objets
27lion = Animal("Simba", 3)
28elephant = Animal("Dumbo", 5)
29
30print(lion.name)  # "Simba" - attribut d'instance
31print(Animal.get_species_count())  # 2 - méthode de classe
32print(Animal.is_adult(3))  # True - méthode statique

1. Définir une classe - class

Syntaxe : class ClassName:

Conventions de nommage :

  • PascalCase (chaque mot avec une majuscule) : Species, AnimalBehavior, DataProcessor
  • Nom descriptif : ce que la classe représente
  • Un nom, pas un verbe : Lion, pas RunsLion
1# Bons noms
2class Species:
3    pass
4
5class ExpeditionTeam:
6    pass
7
8class GeneticAnalyzer:
9    pass
10
11# Mauvais noms
12class species:  # minuscule
13    pass
14
15class data:  # trop générique
16    pass
17
18class doSomething:  # camelCase + verbe
19    pass

2. Constructeur - __init__

Le constructeur (__init__) est une méthode spéciale appelée automatiquement lors de la création d'un objet.

1class Species:
2    def __init__(self, name, habitat):
3        """
4        Constructeur - initialise une nouvelle espèce
5
6        Args:
7            name: Nom de l'espèce
8            habitat: Environnement naturel
9        """
10        print(f"Création d'une nouvelle espèce : {name}")
11        self.name = name  # Attribut d'instance
12        self.habitat = habitat
13        self.discovered_date = "2024-01-01"  # Valeur par défaut
14
15# Appel du constructeur
16lion = Species("Lion", "savane")
17# Appelle automatiquement : Species.__init__(lion, "Lion", "savane")
18# Affiche : "Création d'une nouvelle espèce : Lion"
19
20print(lion.name)  # "Lion"
21print(lion.habitat)  # "savane"

Le paramètre self

self est une référence à l'objet courant. Python passe automatiquement l'objet comme premier argument !

1class Animal:
2    def __init__(self, name):
3        self.name = name  # self = objet courant
4        # self.name signifie « l'attribut 'name' de CET objet »
5
6    def greet(self):
7        # self permet d'accéder aux attributs de l'objet
8        return f"Salut, je suis {self.name} !"
9
10lion = Animal("Simba")
11# Appel : lion.greet()
12# Python fait automatiquement : Animal.greet(lion)  ← lion devient self !
13
14elephant = Animal("Dumbo")
15# elephant.greet() → Animal.greet(elephant) ← elephant est self !
16
17print(lion.greet())     # "Salut, je suis Simba !"
18print(elephant.greet()) # "Salut, je suis Dumbo !"

REMARQUE : tu peux utiliser un autre nom que self, mais c'est une forte convention Python !

1# Standard (utilise ceci !)
2def __init__(self, name):
3    self.name = name
4
5# Techniquement correct, mais À NE PAS FAIRE
6def __init__(this, name):  # Comme en Java/C#
7    this.name = name
8
9def __init__(obj, name):  # Ambigu
10    obj.name = name

3. Attributs d'instance et attributs de classe

Attributs d'instance

Les attributs d'instance appartiennent à un objet concret. Chaque objet a ses propres valeurs !

1class Species:
2    def __init__(self, name, count):
3        self.name = name  # Attribut d'instance
4        self.count = count  # Chaque objet a sa propre valeur
5
6lion = Species("Lion", 500)
7elephant = Species("Éléphant", 300)
8
9print(lion.count)     # 500 - valeur distincte
10print(elephant.count) # 300 - valeur distincte
11
12lion.count = 550  # Modifie SEULEMENT le lion
13print(lion.count)     # 550
14print(elephant.count) # 300 - inchangé !

Attributs de classe

Les attributs de classe appartiennent à la classe et sont partagés par tous les objets !

1class Species:
2    total_species = 0  # Attribut de classe - partagé !
3
4    def __init__(self, name):
5        self.name = name  # Attribut d'instance
6        Species.total_species += 1  # Incrémente le compteur partagé
7
8lion = Species("Lion")
9elephant = Species("Éléphant")
10python = Species("Python")
11
12print(Species.total_species)  # 3 - la classe les connaît tous !
13print(lion.total_species)     # 3 - accessible aussi via l'instance
14print(elephant.total_species) # 3 - même valeur pour tous

Lequel utiliser ?

Type d'attributQuand l'utiliserExemple
InstanceDonnées propres à l'objetself.name, self.age
ClasseDonnées partagéescompteurs, constantes, configuration
1class Species:
2    # Attributs de classe
3    kingdom = "Animalia"  # Constante pour tous
4    species_count = 0  # Compteur
5
6    def __init__(self, name, population):
7        # Attributs d'instance
8        self.name = name  # Unique pour chacun
9        self.population = population
10        Species.species_count += 1
11
12lion = Species("Lion", 500)
13elephant = Species("Éléphant", 300)
14
15print(Species.kingdom)  # "Animalia" - partagé
16print(lion.name)  # "Lion" - unique
17print(elephant.name)  # "Éléphant" - différent

4. Méthodes d'instance

Les méthodes d'instance opèrent sur un objet concret et ont accès à self.

1class Species:
2    def __init__(self, name, population):
3        self.name = name
4        self.population = population
5        self.observations = []
6
7    def add_observation(self, location, count):
8        """Ajoute une observation - méthode d'instance"""
9        self.observations.append({
10            "location": location,
11            "count": count
12        })
13        print(f"Observation {self.name} : {count} à {location}")
14
15    def get_total_observed(self):
16        """Calcule le nombre total d'observations"""
17        return sum(obs["count"] for obs in self.observations)
18
19    def is_endangered(self):
20        """Vérifie si en danger (< 100 individus)"""
21        return self.population < 100
22
23# Utilisation
24lion = Species("Lion", 500)
25lion.add_observation("Serengeti", 25)
26lion.add_observation("Masai Mara", 18)
27
28print(f"Total observé : {lion.get_total_observed()}")  # 43
29print(f"En danger ? {lion.is_endangered()}")  # False

5. Méthodes de classe - @classmethod

Les méthodes de classe opèrent sur toute la classe, pas sur un objet concret. Elles ont accès à cls.

1class Species:
2    all_species = []  # Registre de toutes les espèces
3
4    def __init__(self, name):
5        self.name = name
6        Species.all_species.append(self)
7
8    @classmethod
9    def get_species_count(cls):
10        """Renvoie le nombre de toutes les espèces"""
11        return len(cls.all_species)
12
13    @classmethod
14    def find_by_name(cls, name):
15        """Trouve une espèce par son nom dans le registre de la classe"""
16        for species in cls.all_species:
17            if species.name == name:
18                return species
19        return None
20
21    @classmethod
22    def clear_registry(cls):
23        """Vide le registre"""
24        cls.all_species.clear()
25
26# Utilisation
27lion = Species("Lion")
28elephant = Species("Éléphant")
29python = Species("Python")
30
31print(Species.get_species_count())  # 3 - méthode de classe
32
33found = Species.find_by_name("Éléphant")
34print(found.name)  # "Éléphant"

Différence self / cls :

  • self = objet concret (instance)
  • cls = classe (type)

6. Méthodes statiques - @staticmethod

Les méthodes statiques n'ont PAS accès à self ni à cls. Ce sont des fonctions ordinaires dans l'espace de noms de la classe.

1class Species:
2    @staticmethod
3    def is_valid_name(name):
4        """Vérifie si le nom de l'espèce est valide"""
5        # Pas de self, pas de cls - seulement des arguments !
6        return (
7            len(name) > 0 and
8            name[0].isupper() and
9            " " not in name
10        )
11
12    @staticmethod
13    def calculate_biodiversity_index(species_count, area_km2):
14        """Calcule l'indice de biodiversité"""
15        return species_count / area_km2 if area_km2 > 0 else 0
16
17# Utilisation - pas besoin d'objet !
18print(Species.is_valid_name("Lion"))  # True
19print(Species.is_valid_name("lion"))  # False - minuscule
20print(Species.is_valid_name(""))     # False - vide
21
22index = Species.calculate_biodiversity_index(150, 1000)
23print(f"Indice de biodiversité : {index}")  # 0.15

Quand utiliser les méthodes statiques ?

  • Fonctions utilitaires liées thématiquement à la classe
  • Validateurs, convertisseurs, calculateurs
  • Elles n'ont pas besoin d'accéder aux attributs de l'objet ou de la classe

Comparaison des types de méthodes

Réunissons les trois types de méthodes dans une seule classe et voyons lesquelles s'appellent sur la classe elle-même et lesquelles ont besoin d'un objet.

1class Species:
2    species_count = 0  # Attribut de classe
3
4    def __init__(self, name):
5        self.name = name
6        Species.species_count += 1
7
8    # 1. Méthode d'instance - a self
9    def describe(self):
10        """Accès à self.name"""
11        return f"Espèce : {self.name}"
12
13    # 2. Méthode de classe - a cls
14    @classmethod
15    def get_count(cls):
16        """Accès à cls.species_count"""
17        return cls.species_count
18
19    # 3. Méthode statique - pas de self/cls
20    @staticmethod
21    def is_valid_name(name):
22        """Seulement des arguments, pas d'accès à la classe/l'objet"""
23        return len(name) > 0
24
25lion = Species("Lion")
26
27# 1. Méthode d'instance - a besoin d'un objet
28print(lion.describe())  # OK - opère sur l'objet
29# print(Species.describe())  # ERREUR - pas d'objet !
30
31# 2. Méthode de classe - opère sur la classe
32print(Species.get_count())  # OK - opère sur la classe
33print(lion.get_count())  # OK - fonctionne aussi (Python passe la classe)
34
35# 3. Méthode statique - fonctionne toujours
36print(Species.is_valid_name("Lion"))  # OK
37print(lion.is_valid_name("Lion"))  # OK - fonctionne aussi

Exemple Safari - Système de catalogage des espèces

Ce catalogue réunit tout ce que tu as vu dans cette leçon : les attributs de classe tiennent un registre commun des espèces, les méthodes d'instance gèrent une seule espèce, les méthodes de classe tout le catalogue, et les méthodes statiques sont des outils d'aide, comme la validation d'un nom scientifique.

1class SpeciesCatalog:
2    """
3    Système complet de catalogage des espèces Safari
4
5    Démontre tous les concepts : attributs, méthodes, constructeur
6    """
7
8    # Attribut de classe - registre de toutes les espèces
9    all_species = {}  # {name: objet Species}
10    next_id = 1
11
12    def __init__(self, scientific_name, common_name, habitat, diet, dangerous=False):
13        """
14        Constructeur - crée une nouvelle espèce dans le catalogue
15
16        Args:
17            scientific_name: Nom scientifique (par ex. "Panthera leo")
18            common_name: Nom courant (par ex. "Lion")
19            habitat: Environnement ("savane", "jungle", "montagnes", etc.)
20            diet: Régime ("carnivore", "herbivore", "omnivore")
21            dangerous: Dangereux pour l'humain ou non
22        """
23        # Attributs d'instance - uniques pour chaque espèce
24        self.id = SpeciesCatalog.next_id
25        SpeciesCatalog.next_id += 1
26
27        self.scientific_name = scientific_name
28        self.common_name = common_name
29        self.habitat = habitat
30        self.diet = diet
31        self.dangerous = dangerous
32
33        self.observations = []  # Liste des observations
34        self.population_estimate = 0
35        self.conservation_status = "Inconnu"  # Inconnu par défaut
36
37        # Ajout au registre de la classe
38        SpeciesCatalog.all_species[scientific_name] = self
39
40    # === MÉTHODES D'INSTANCE (opèrent sur une espèce concrète) ===
41
42    def add_observation(self, date, location, count, notes=""):
43        """Ajoute une observation de terrain de l'espèce"""
44        observation = {
45            "date": date,
46            "location": location,
47            "count": count,
48            "notes": notes
49        }
50        self.observations.append(observation)
51        self.population_estimate += count
52        return f"Observation ajoutée : {count}x {self.common_name}"
53
54    def get_total_observed(self):
55        """Renvoie le nombre total d'individus observés"""
56        return sum(obs["count"] for obs in self.observations)
57
58    def update_conservation_status(self):
59        """Met à jour le statut de conservation d'après la population"""
60        total = self.get_total_observed()
61
62        if total == 0:
63            self.conservation_status = "Non observée"
64        elif total < 50:
65            self.conservation_status = "En danger critique"
66        elif total < 250:
67            self.conservation_status = "En danger"
68        elif total < 1000:
69            self.conservation_status = "Vulnérable"
70        else:
71            self.conservation_status = "Préoccupation mineure"
72
73        return self.conservation_status
74
75    def get_risk_level(self):
76        """Calcule le niveau de risque pour l'équipe (0-10)"""
77        if not self.dangerous:
78            return 0
79
80        # Niveau de risque de base pour les espèces dangereuses
81        risk = 5
82
83        # Augmente le risque si carnivore
84        if self.diet == "carnivore":
85            risk += 3
86
87        # Diminue si la population est faible (rarement rencontrée)
88        if self.get_total_observed() < 50:
89            risk -= 2
90
91        return max(0, min(10, risk))  # Borne entre 0 et 10
92
93    def generate_report(self):
94        """Génère un rapport d'espèce"""
95        self.update_conservation_status()
96
97        report = f"""
98┌─────────────────────────────────────────────────────────┐
99│ ID : {self.id:03d} - {self.common_name.upper()}
100├─────────────────────────────────────────────────────────┤
101│ Nom scientifique : {self.scientific_name}
102│ Habitat : {self.habitat}
103│ Régime : {self.diet}
104│ Dangereuse : {'OUI' if self.dangerous else 'Non'}
105│ Niveau de risque : {self.get_risk_level()}/10
106├─────────────────────────────────────────────────────────┤
107│ OBSERVATIONS :
108│ - Total : {self.get_total_observed()} individus
109│ - Nombre d'observations : {len(self.observations)}
110│ - Statut de conservation : {self.conservation_status}
111└─────────────────────────────────────────────────────────┘
112        """.strip()
113        return report
114
115    # === MÉTHODES DE CLASSE (opèrent sur tout le catalogue) ===
116
117    @classmethod
118    def get_total_species(cls):
119        """Renvoie le nombre de toutes les espèces enregistrées"""
120        return len(cls.all_species)
121
122    @classmethod
123    def find_by_name(cls, scientific_name):
124        """Trouve une espèce par son nom scientifique"""
125        return cls.all_species.get(scientific_name)
126
127    @classmethod
128    def get_by_habitat(cls, habitat):
129        """Renvoie toutes les espèces d'un habitat donné"""
130        return [
131            species for species in cls.all_species.values()
132            if species.habitat == habitat
133        ]
134
135    @classmethod
136    def get_dangerous_species(cls):
137        """Renvoie toutes les espèces dangereuses"""
138        return [
139            species for species in cls.all_species.values()
140            if species.dangerous
141        ]
142
143    @classmethod
144    def get_most_observed(cls, n=5):
145        """Renvoie les n espèces les plus observées"""
146        sorted_species = sorted(
147            cls.all_species.values(),
148            key=lambda s: s.get_total_observed(),
149            reverse=True
150        )
151        return sorted_species[:n]
152
153    @classmethod
154    def clear_catalog(cls):
155        """Vide tout le catalogue (à utiliser avec prudence !)"""
156        cls.all_species.clear()
157        cls.next_id = 1
158        return "Catalogue vidé"
159
160    # === MÉTHODES STATIQUES (outils d'aide) ===
161
162    @staticmethod
163    def validate_scientific_name(name):
164        """Valide un nom scientifique (format : "Genre espèce")"""
165        parts = name.split()
166        if len(parts) != 2:
167            return False
168
169        genus, species = parts
170        # Genre avec majuscule, espèce en minuscule
171        return genus[0].isupper() and species[0].islower()
172
173    @staticmethod
174    def calculate_biodiversity_index(species_list):
175        """Calcule l'indice de diversité de Simpson"""
176        if not species_list:
177            return 0.0
178
179        total = sum(s.get_total_observed() for s in species_list)
180        if total == 0:
181            return 0.0
182
183        # Indice de Simpson : 1 - Σ(ni/N)²
184        sum_squares = sum(
185            (s.get_total_observed() / total) ** 2
186            for s in species_list
187        )
188        return 1 - sum_squares
189
190    @staticmethod
191    def format_scientific_name(genus, species):
192        """Formate un nom scientifique : genre avec une majuscule, espèce en minuscules"""
193        return f"{genus.capitalize()} {species.lower()}"
194
195# === DÉMONSTRATION D'UTILISATION ===
196
197print("=== CRÉATION DU CATALOGUE D'ESPÈCES ===\n")
198
199# Création des espèces (constructeur)
200lion = SpeciesCatalog(
201    scientific_name="Panthera leo",
202    common_name="Lion",
203    habitat="savane",
204    diet="carnivore",
205    dangerous=True
206)
207
208elephant = SpeciesCatalog(
209    scientific_name="Loxodonta africana",
210    common_name="Éléphant d'Afrique",
211    habitat="savane",
212    diet="herbivore",
213    dangerous=False
214)
215
216python = SpeciesCatalog(
217    scientific_name="Python regius",
218    common_name="Python royal",
219    habitat="jungle",
220    diet="carnivore",
221    dangerous=False
222)
223
224crocodile = SpeciesCatalog(
225    scientific_name="Crocodylus niloticus",
226    common_name="Crocodile du Nil",
227    habitat="rivière",
228    diet="carnivore",
229    dangerous=True
230)
231
232# Ajout d'observations (méthodes d'instance)
233lion.add_observation("2024-01-15", "Serengeti", 12, "Troupe de chasse")
234lion.add_observation("2024-01-20", "Masai Mara", 8, "Famille avec des petits")
235elephant.add_observation("2024-01-16", "Amboseli", 45, "Grand troupeau")
236python.add_observation("2024-01-18", "Jungle du Congo", 3)
237crocodile.add_observation("2024-01-19", "Nil", 25, "Sur la berge")
238
239# Rapports pour chaque espèce
240print(lion.generate_report())
241print()
242print(elephant.generate_report())
243
244# Méthodes de classe - statistiques sur tout le catalogue
245print(f"\n=== STATISTIQUES DU CATALOGUE ===")
246print(f"Nombre total d'espèces : {SpeciesCatalog.get_total_species()}")
247
248print(f"\nEspèces de la savane :")
249for species in SpeciesCatalog.get_by_habitat("savane"):
250    print(f"  - {species.common_name} ({species.get_total_observed()} individus)")
251
252print(f"\nEspèces dangereuses :")
253for species in SpeciesCatalog.get_dangerous_species():
254    risk = species.get_risk_level()
255    print(f"   {species.common_name} - Risque : {risk}/10")
256
257print(f"\nTop 3 des plus observées :")
258for i, species in enumerate(SpeciesCatalog.get_most_observed(3), 1):
259    print(f"  {i}. {species.common_name} : {species.get_total_observed()} individus")
260
261# Méthodes statiques - outils
262print(f"\n=== VALIDATION ===")
263names_to_test = ["Panthera leo", "panthera leo", "Lion", "Canis lupus familiaris"]
264for name in names_to_test:
265    valid = "[OK]" if SpeciesCatalog.validate_scientific_name(name) else "[INVALIDE]"
266    print(f"{valid} {name}")
267
268# Indice de biodiversité
269all_species_list = list(SpeciesCatalog.all_species.values())
270biodiversity = SpeciesCatalog.calculate_biodiversity_index(all_species_list)
271print(f"\nIndice de biodiversité : {biodiversity:.3f}")

Résumé

Dans cette leçon, tu as appris :

  • Comment définir des classes avec class
  • Créer un constructeur avec __init__
  • Utiliser self pour accéder aux attributs
  • La différence entre attributs d'instance et attributs de classe
  • Créer des méthodes d'instance, de classe et statiques
  • Quand utiliser chaque type de méthode
  • Construire un système complet de catalogage

Point de contrôle

Avant de passer à la suite :

  • Tu sais définir une classe avec un constructeur
  • Tu comprends la différence entre self et cls
  • Tu connais la différence entre attributs d'instance et attributs de classe
  • Tu sais quand utiliser @classmethod ou @staticmethod
  • Tu sais créer un objet et appeler ses méthodes

Concept clé : self c'est « moi » - l'objet se réfère à lui-même pour accéder à ses propres données et comportements !

Dans la prochaine leçon, Darwin t'apprendra l'héritage - comment créer des hiérarchies de classes comme un arbre évolutif !

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Un attribut de classe est partagé par :

  2. 2. Par convention, comment s'appelle le premier paramètre d'une méthode d'instance ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Clique sur les éléments dans le bon ordre :

  • Éditeur de code

    Ajoute à la classe Animal une méthode speak() qui affiche (print) le cri de l'animal, par ex. 'Simba : Roar!'. Crée un animal et appelle speak()

  • Éditeur de code

    Dans la classe Animal, ajoute un attribut de classe kingdom = "Animalia" et un attribut d'instance name (défini dans __init__)

  • Tri horizontal

    Range les éléments dans le bon ordre :

Articles utiles