Cours Python · Module 3: Programmation orientée objet
Créer tes propres espèces
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 statique1. 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, pasRunsLion
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 pass2. 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 = name3. 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 tousLequel utiliser ?
| Type d'attribut | Quand l'utiliser | Exemple |
|---|---|---|
| Instance | Données propres à l'objet | self.name, self.age |
| Classe | Données partagées | compteurs, 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érent4. 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()}") # False5. 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.15Quand 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 aussiExemple 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
selfpour 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
selfetcls - Tu connais la différence entre attributs d'instance et attributs de classe
- Tu sais quand utiliser
@classmethodou@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. Un attribut de classe est partagé par :
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 :