Cours Python · Module 3: Programmation orientée objet

Type hints - classification précise

15 min de lecture
Dans cette leçon10

Bon retour ! Darwin ici, avec une leçon sur la précision et la documentation du code.

En biologie, une classification précise est essentielle - « grand animal » n'est pas la même chose que « Panthera leo (lion d'Afrique), mâle, adulte, 190kg ». En Python, les type hints (annotations de type) permettent la même précision dans le code !

1# Imprécis - qu'est-ce que c'est ?
2def count_animals(animals):
3    return len(animals)
4
5# Précis - tout est clair !
6def count_animals(animals: list[str]) -> int:
7    """Compte les animaux dans la liste"""
8    return len(animals)

Que sont les type hints ?

Les type hints sont des annotations de type en Python - des informations supplémentaires sur les types des variables, des paramètres de fonction et des valeurs de retour.

IMPORTANT : les type hints sont optionnels et n'affectent PAS l'exécution du programme ! Python les ignore à l'exécution. Ils servent à :

  • Documentation - expliquer les intentions du programmeur
  • IDE - de meilleures suggestions et l'autocomplétion
  • Outils - mypy, pyright peuvent détecter les erreurs de type
  • Lisibilité - un code plus facile à comprendre
1# Sans type hints - il faut deviner
2def calculate_density(count, area):
3    return count / area
4
5# Avec type hints - tout est clair !
6def calculate_density(count: int, area: float) -> float:
7    """Calcule la densité de population (individus/km²)"""
8    return count / area

Type hints de base

Variables

Tu écris l'annotation d'une variable après deux-points, entre le nom et le signe =. Pour les conteneurs, tu peux aussi indiquer le type des éléments entre crochets :

1# Types de base
2name: str = "Lion"
3age: int = 5
4weight: float = 190.5
5is_dangerous: bool = True
6
7# Types de conteneurs seuls (sans type des éléments)
8animals: list = ["Lion", "Éléphant"]
9habitat_map: dict = {"Lion": "savane"}
10
11# Python 3.9+ - encore mieux avec le type des éléments
12animals: list[str] = ["Lion", "Éléphant", "Girafe"]
13populations: dict[str, int] = {"Lion": 500, "Éléphant": 300}
14coordinates: tuple[float, float] = (51.5, -0.1)
15unique_species: set[str] = {"Lion", "Éléphant"}

Fonctions - paramètres et type de retour

Dans une fonction, tu écris le type de chaque paramètre après deux-points et le type du résultat après la flèche ->, avant le deux-points final :

1def greet(name: str) -> str:
2    """Prend un str, renvoie un str"""
3    return f"Bonjour, {name} !"
4
5def add_numbers(a: int, b: int) -> int:
6    """Prend deux int, renvoie un int"""
7    return a + b
8
9def calculate_average(numbers: list[float]) -> float:
10    """Prend une liste de float, renvoie un float"""
11    return sum(numbers) / len(numbers)
12
13def print_info(message: str) -> None:
14    """None signifie « ne renvoie rien »"""
15    print(message)
16    # pas de return ou return None

Types du module typing

Optional - peut être None

Optional[str] indique que la fonction renvoie du texte ou None. C'est utile pour une recherche qui peut ne rien trouver :

1from typing import Optional
2
3def find_species(name: str) -> Optional[str]:
4    """
5    Renvoie une espèce ou None si non trouvée
6
7    Optional[str] = str | None
8    """
9    database = {"Lion": "Panthera leo", "Éléphant": "Loxodonta africana"}
10    return database.get(name)  # Peut renvoyer un str ou None
11
12result = find_species("Lion")  # Optional[str]
13if result is not None:
14    print(f"Trouvé : {result}")

Union - l'un de plusieurs types

Union énumère plusieurs types autorisés. L'identifiant d'un animal peut être un nombre ou du texte :

1from typing import Union
2
3def process_id(animal_id: Union[int, str]) -> str:
4    """
5    Accepte int OU str
6
7    Python 3.10+ : int | str
8    """
9    return f"ID : {animal_id}"
10
11process_id(123)       # OK - int
12process_id("LEO-45")  # OK - str

Python 3.10+ : tu peux utiliser | à la place de Union !

1def process_id(animal_id: int | str) -> str:
2    """Syntaxe plus récente - Python 3.10+"""
3    return f"ID : {animal_id}"
4
5# Optional peut aussi s'écrire plus court
6def find_species(name: str) -> str | None:
7    """str | None = Optional[str]"""
8    pass

List, Dict, Tuple, Set - avec des types spécifiques

Les conteneurs intégrés prennent le type de leurs éléments entre crochets. Un tuple peut avoir un nombre fixe de positions ou un nombre quelconque, noté ... :

1# Python 3.9+ - utilise les minuscules !
2def process_animals(names: list[str]) -> dict[str, int]:
3    """Liste de chaînes → dictionnaire chaîne:int"""
4    return {name: len(name) for name in names}
5
6# Tuple - nombre fixe d'éléments avec des types précisés
7def get_coordinates() -> tuple[float, float]:
8    """Renvoie (latitude, longitude)"""
9    return (51.5074, -0.1278)
10
11# Tuple - nombre variable d'éléments du même type
12def get_observations() -> tuple[int, ...]:
13    """Tuple avec un nombre quelconque d'int"""
14    return (12, 45, 23, 67, 89)
15
16# Set
17def get_unique_habitats() -> set[str]:
18    """Ensemble d'habitats uniques"""
19    return {"savane", "jungle", "montagnes"}

Any - n'importe quel type

Any signifie que la valeur peut être de n'importe quel type, donc les outils arrêtent de la vérifier :

1from typing import Any
2
3def log_data(data: Any) -> None:
4    """Accepte n'importe quoi - à utiliser avec parcimonie !"""
5    print(f"Journalisation : {data}")
6
7log_data(123)           # OK
8log_data("texte")       # OK
9log_data([1, 2, 3])     # OK
10log_data({"key": "val"})  # OK

Remarque : n'utilise Any que lorsque tu acceptes vraiment n'importe quel type ! C'est comme renoncer aux type hints.

Type hints pour les classes

Dans une classe, tu annotes les attributs de classe, les paramètres de __init__ et des méthodes, ainsi que les attributs d'instance. Un nom de classe que Python ne connaît pas encore s'écrit entre guillemets, par ex. 'Species' :

1class Species:
2    """Species avec type hints"""
3
4    # Attributs de classe
5    kingdom: str = "Animalia"
6    all_species: list['Species'] = []  # Forward reference
7
8    def __init__(
9        self,
10        scientific_name: str,
11        common_name: str,
12        population: int,
13        endangered: bool = False
14    ) -> None:
15        """Constructeur avec type hints"""
16        self.scientific_name: str = scientific_name
17        self.common_name: str = common_name
18        self.population: int = population
19        self.endangered: bool = endangered
20        self.observations: list[dict[str, Any]] = []
21
22        Species.all_species.append(self)
23
24    def add_observation(
25        self,
26        location: str,
27        count: int,
28        date: str | None = None
29    ) -> None:
30        """Ajoute une observation - type de retour None"""
31        obs: dict[str, str | int] = {
32            "location": location,
33            "count": count
34        }
35        if date:
36            obs["date"] = date
37        self.observations.append(obs)
38
39    def get_population_trend(self) -> float:
40        """Renvoie un float"""
41        if len(self.observations) < 2:
42            return 0.0
43
44        first = self.observations[0]["count"]
45        last = self.observations[-1]["count"]
46        return (last - first) / first * 100
47
48    @classmethod
49    def get_endangered_species(cls) -> list['Species']:
50        """Renvoie une liste de Species - forward reference !"""
51        return [s for s in cls.all_species if s.endangered]
52
53    @staticmethod
54    def is_valid_population(pop: int) -> bool:
55        """Renvoie un bool"""
56        return pop >= 0

Type hints avancés

Callable - les fonctions comme types

Quand un paramètre est une fonction, Callable[[Species], bool] décrit ce que cette fonction reçoit et ce qu'elle renvoie :

1from typing import Callable
2
3def apply_filter(
4    species_list: list[Species],
5    filter_func: Callable[[Species], bool]
6) -> list[Species]:
7    """
8    Filtre une liste par une fonction
9
10    Callable[[Species], bool] = fonction prenant un Species, renvoyant un bool
11    """
12    return [s for s in species_list if filter_func(s)]
13
14# Utilisation
15def is_endangered(species: Species) -> bool:
16    return species.endangered
17
18endangered = apply_filter(Species.all_species, is_endangered)

TypeAlias - alias pour les types complexes

Tu peux nommer une seule fois un type long et réutiliser ce nom à plusieurs endroits. Un tel alias se crée avec l'annotation TypeAlias :

1from typing import TypeAlias
2
3# Alias pour la lisibilité
4PopulationData: TypeAlias = dict[str, list[int]]
5Coordinates: TypeAlias = tuple[float, float]
6SpeciesDict: TypeAlias = dict[str, Species]
7
8def analyze_populations(data: PopulationData) -> float:
9    """Le type PopulationData est plus lisible que dict[str, list[int]]"""
10    all_counts: list[int] = []
11    for counts in data.values():
12        all_counts.extend(counts)
13    return sum(all_counts) / len(all_counts)
14
15def get_location(name: str) -> Coordinates:
16    """Coordinates indique clairement ce que l'on renvoie"""
17    locations: dict[str, Coordinates] = {
18        "Serengeti": (-2.3333, 34.8333),
19        "Masai Mara": (-1.5, 35.1667)
20    }
21    return locations.get(name, (0.0, 0.0))

Generic - classes génériques

Une classe générique reçoit le type de ses éléments en paramètre, comme list[str]. La même pile peut contenir des chaînes ou des nombres, et l'IDE sait quand même ce que tu vas en retirer :

1from typing import Generic, TypeVar
2
3T = TypeVar('T')  # Type générique
4
5class Stack(Generic[T]):
6    """Pile générique - peut stocker n'importe quel type"""
7
8    def __init__(self) -> None:
9        self._items: list[T] = []
10
11    def push(self, item: T) -> None:
12        """Ajoute un élément de type T"""
13        self._items.append(item)
14
15    def pop(self) -> T:
16        """Retire et renvoie un élément de type T"""
17        return self._items.pop()
18
19    def is_empty(self) -> bool:
20        return len(self._items) == 0
21
22# Utilisation
23string_stack: Stack[str] = Stack[str]()
24string_stack.push("Lion")
25string_stack.push("Éléphant")
26animal: str = string_stack.pop()  # L'IDE sait que c'est un str !
27
28int_stack: Stack[int] = Stack[int]()
29int_stack.push(123)
30int_stack.push(456)
31number: int = int_stack.pop()  # L'IDE sait que c'est un int !

Exemple Safari - système complet avec type hints

Maintenant tout ensemble : des alias de types, des enums pour le statut de conservation et l'habitat, une classe annotée dans chaque méthode et des fonctions d'aide qui l'utilisent.

1from typing import Optional, TypeAlias
2from datetime import date
3from enum import Enum
4
5# === ALIAS DE TYPE ===
6
7SpeciesID: TypeAlias = str
8Coordinates: TypeAlias = tuple[float, float]
9ObservationData: TypeAlias = dict[str, str | int | date]
10
11# === ENUMS ===
12
13class ConservationStatus(Enum):
14    """Statut de conservation d'une espèce"""
15    EXTINCT = "extinct"
16    EXTINCT_IN_WILD = "extinct_in_wild"
17    CRITICALLY_ENDANGERED = "critically_endangered"
18    ENDANGERED = "endangered"
19    VULNERABLE = "vulnerable"
20    NEAR_THREATENED = "near_threatened"
21    LEAST_CONCERN = "least_concern"
22
23class Habitat(Enum):
24    """Type d'habitat"""
25    SAVANNA = "savanna"
26    JUNGLE = "jungle"
27    MOUNTAINS = "mountains"
28    DESERT = "desert"
29    WETLANDS = "wetlands"
30
31# === CLASSE PRINCIPALE ===
32
33class Species:
34    """
35    Classe Species avec des type hints complets
36
37    Une classification précise comme en biologie !
38    """
39
40    # Attribut de classe
41    _registry: dict[SpeciesID, 'Species'] = {}
42
43    def __init__(
44        self,
45        scientific_name: str,
46        common_name: str,
47        population: int,
48        habitat: Habitat,
49        status: ConservationStatus,
50        dangerous: bool = False
51    ) -> None:
52        """
53        Initialise l'espèce
54
55        Args:
56            scientific_name: Nom scientifique (par ex. "Panthera leo")
57            common_name: Nom courant (par ex. "Lion")
58            population: Nombre d'individus
59            habitat: Type d'habitat (enum)
60            status: Statut de conservation (enum)
61            dangerous: Dangereuse pour l'humain ou non
62        """
63        self.id: SpeciesID = scientific_name
64        self.scientific_name: str = scientific_name
65        self.common_name: str = common_name
66        self.population: int = population
67        self.habitat: Habitat = habitat
68        self.status: ConservationStatus = status
69        self.dangerous: bool = dangerous
70
71        # Liste des observations
72        self.observations: list[ObservationData] = []
73
74        # Emplacements
75        self.locations: set[Coordinates] = set()
76
77        # Enregistrement
78        Species._registry[self.id] = self
79
80    def add_observation(
81        self,
82        location: str,
83        coordinates: Coordinates,
84        count: int,
85        observation_date: date | None = None
86    ) -> None:
87        """
88        Ajoute une observation d'espèce
89
90        Args:
91            location: Nom de l'emplacement
92            coordinates: (latitude, longitude)
93            count: Nombre d'individus observés
94            observation_date: Date de l'observation (optionnelle)
95        """
96        obs: ObservationData = {
97            "location": location,
98            "count": count,
99            "date": observation_date or date.today()
100        }
101        self.observations.append(obs)
102        self.locations.add(coordinates)
103
104    def get_total_observed(self) -> int:
105        """Renvoie le nombre total d'individus observés"""
106        return sum(
107            int(obs["count"])
108            for obs in self.observations
109        )
110
111    def get_observation_locations(self) -> list[str]:
112        """Renvoie la liste des emplacements d'observation uniques"""
113        locations: set[str] = {
114            str(obs["location"])
115            for obs in self.observations
116        }
117        return sorted(locations)
118
119    def is_threatened(self) -> bool:
120        """Vérifie si l'espèce est menacée"""
121        threatened_statuses: set[ConservationStatus] = {
122            ConservationStatus.CRITICALLY_ENDANGERED,
123            ConservationStatus.ENDANGERED,
124            ConservationStatus.VULNERABLE
125        }
126        return self.status in threatened_statuses
127
128    def get_risk_assessment(self) -> dict[str, str | int | bool]:
129        """
130        Évaluation du risque pour l'expédition
131
132        Returns:
133            Dictionnaire avec l'évaluation du risque
134        """
135        risk_level: int = 0
136
137        if self.dangerous:
138            risk_level += 5
139
140        if self.population < 100:
141            risk_level += 3  # Rare - difficile à trouver
142
143        if self.habitat == Habitat.JUNGLE:
144            risk_level += 2  # Terrain difficile
145
146        return {
147            "species": self.common_name,
148            "risk_level": min(10, risk_level),
149            "dangerous": self.dangerous,
150            "rare": self.population < 100,
151            "recommendation": "Extrême prudence" if risk_level >= 7 else "Protocole normal"
152        }
153
154    @classmethod
155    def get_by_id(cls, species_id: SpeciesID) -> Optional['Species']:
156        """
157        Trouve une espèce par son ID
158
159        Returns:
160            Species ou None si non trouvée
161        """
162        return cls._registry.get(species_id)
163
164    @classmethod
165    def get_by_habitat(cls, habitat: Habitat) -> list['Species']:
166        """Renvoie toutes les espèces d'un habitat donné"""
167        return [
168            species for species in cls._registry.values()
169            if species.habitat == habitat
170        ]
171
172    @classmethod
173    def get_endangered(cls) -> list['Species']:
174        """Renvoie toutes les espèces menacées"""
175        return [
176            species for species in cls._registry.values()
177            if species.is_threatened()
178        ]
179
180    @staticmethod
181    def calculate_biodiversity_index(
182        species_list: list['Species']
183    ) -> float:
184        """
185        Calcule l'indice de biodiversité de Simpson
186
187        Args:
188            species_list: Liste des espèces à analyser
189
190        Returns:
191            Indice (0.0 - 1.0, plus élevé = plus grande diversité)
192        """
193        if not species_list:
194            return 0.0
195
196        total: int = sum(s.population for s in species_list)
197        if total == 0:
198            return 0.0
199
200        sum_squares: float = sum(
201            (s.population / total) ** 2
202            for s in species_list
203        )
204
205        return 1.0 - sum_squares
206
207    def __str__(self) -> str:
208        return f"{self.common_name} ({self.population} individus)"
209
210    def __repr__(self) -> str:
211        return (f"Species(scientific_name='{self.scientific_name}', "
212                f"population={self.population}, "
213                f"habitat={self.habitat.value})")
214
215# === FONCTIONS UTILITAIRES ===
216
217def generate_report(species: Species) -> str:
218    """
219    Génère un rapport d'espèce
220
221    Args:
222        species: Espèce sur laquelle faire le rapport
223
224    Returns:
225        Rapport textuel formaté
226    """
227    threatened: str = "OUI" if species.is_threatened() else "Non"
228    risk: dict[str, str | int | bool] = species.get_risk_assessment()
229
230    report: str = f"""
231╔══════════════════════════════════════════════════╗
232  {species.common_name.upper()}
233╠══════════════════════════════════════════════════╣
234  Nom scientifique : {species.scientific_name}
235  Population : {species.population} individus
236  Habitat : {species.habitat.value}
237  Statut : {species.status.value}
238  Menacée : {threatened}
239  Dangereuse : {"OUI" if species.dangerous else "Non"}
240  Niveau de risque : {risk['risk_level']}/10
241  Observations : {len(species.observations)}
242  Emplacements : {', '.join(species.get_observation_locations()) or 'Aucun'}
243╚══════════════════════════════════════════════════╝
244    """.strip()
245
246    return report
247
248def filter_by_population(
249    species_list: list[Species],
250    min_pop: int,
251    max_pop: int | None = None
252) -> list[Species]:
253    """
254    Filtre les espèces par population
255
256    Args:
257        species_list: Liste des espèces
258        min_pop: Population minimale
259        max_pop: Population maximale (optionnelle)
260
261    Returns:
262        Liste filtrée
263    """
264    filtered: list[Species] = [
265        s for s in species_list
266        if s.population >= min_pop
267    ]
268
269    if max_pop is not None:
270        filtered = [s for s in filtered if s.population <= max_pop]
271
272    return filtered
273
274# === DÉMONSTRATION ===
275
276print("=== CATALOGUE D'ESPÈCES AVEC TYPE HINTS ===\n")
277
278# Création des espèces
279lion = Species(
280    scientific_name="Panthera leo",
281    common_name="Lion",
282    population=120,
283    habitat=Habitat.SAVANNA,
284    status=ConservationStatus.VULNERABLE,
285    dangerous=True
286)
287
288rhino = Species(
289    scientific_name="Diceros bicornis",
290    common_name="Rhinocéros noir",
291    population=45,
292    habitat=Habitat.SAVANNA,
293    status=ConservationStatus.CRITICALLY_ENDANGERED,
294    dangerous=True
295)
296
297elephant = Species(
298    scientific_name="Loxodonta africana",
299    common_name="Éléphant d'Afrique",
300    population=450,
301    habitat=Habitat.SAVANNA,
302    status=ConservationStatus.ENDANGERED,
303    dangerous=False
304)
305
306# Ajout d'observations
307lion.add_observation("Serengeti", (-2.3333, 34.8333), 12)
308lion.add_observation("Masai Mara", (-1.5, 35.1667), 8)
309rhino.add_observation("Ngorongoro", (-3.1792, 35.5500), 3)
310elephant.add_observation("Amboseli", (-2.6527, 37.2606), 35)
311
312# Rapports
313print(generate_report(lion))
314print()
315print(generate_report(rhino))
316
317# Filtrage
318print("\n=== ESPÈCES MENACÉES ===")
319endangered: list[Species] = Species.get_endangered()
320for species in endangered:
321    print(f"   {species.common_name} : {species.status.value}")
322
323# Filtre par population
324print("\n=== ESPÈCES AVEC POPULATION < 100 ===")
325rare: list[Species] = filter_by_population(
326    list(Species._registry.values()),
327    min_pop=0,
328    max_pop=99
329)
330for species in rare:
331    print(f"  - {species.common_name} : {species.population} individus")
332
333# Indice de biodiversité
334biodiversity: float = Species.calculate_biodiversity_index(
335    list(Species._registry.values())
336)
337print(f"\nIndice de biodiversité : {biodiversity:.3f}")

Outils de vérification des types

mypy - vérification statique des types

L'outil mypy lit les annotations et vérifie les types avant que tu lances le programme. Tu l'installes avec pip et tu lui indiques le fichier à vérifier :

1# Installation
2pip install mypy
3
4# Vérifier un fichier
5mypy script.py

Python exécute ce code sans erreur, mais mypy le bloque :

1def add(a: int, b: int) -> int:
2    return a + b
3
4result: str = add(5, 10)  # mypy : Incompatible types in assignment

La fonction renvoie int, mais la variable est déclarée en str. Python ne vérifie pas les annotations pendant l'exécution, donc seul mypy ou ton éditeur repérera cette erreur.

Vérification des types dans les IDE

Les IDE modernes (VS Code, PyCharm) vérifient automatiquement les type hints :

  • Autocomplétion - suggestions basées sur les types
  • Détection d'erreurs - détection des erreurs de type
  • Refactorisation - des changements plus sûrs
  • Documentation - aperçu rapide de la signature

Bonnes pratiques des type hints

1. Ajoute toujours des type hints aux API publiques

Les méthodes utilisées par le reste du programme sont un contrat avec les autres programmeurs. Les annotations disent tout de suite ce que ces méthodes attendent et ce qu'elles renvoient :

1# Bon - API publique avec type hints
2class Species:
3    def add_observation(self, location: str, count: int) -> None:
4        pass
5
6# Mauvais - API publique sans types
7class Species:
8    def add_observation(self, location, count):
9        pass

2. Utilise des types spécifiques plutôt que Any

Any n'apprend rien au lecteur et ne vérifie rien. Si tu sais quelles données arrivent, écris-le dans l'annotation :

1# Mauvais - trop générique
2def process_data(data: Any) -> Any:
3    return data
4
5# Bon - types spécifiques
6def process_data(data: dict[str, int]) -> list[int]:
7    return list(data.values())

3. Utilise Optional pour les valeurs None

Si une fonction peut renvoyer None, indique-le dans l'annotation. Ainsi, quiconque l'appelle sait qu'il doit vérifier le résultat :

1# Bon - clairement précisé
2def find_species(name: str) -> Species | None:
3    pass
4
5# Trompeur - renvoie-t-elle None ?
6def find_species(name: str) -> Species:
7    pass

4. Documente les types complexes avec TypeAlias

Quand le même type complexe apparaît à plusieurs endroits, donne-lui un nom. La signature de la fonction devient plus courte et son sens saute aux yeux :

1# Lisible
2ObservationData: TypeAlias = dict[str, str | int | date]
3
4def add_observation(data: ObservationData) -> None:
5    pass
6
7# Illisible
8def add_observation(data: dict[str, str | int | date]) -> None:
9    pass

Résumé

Dans cette leçon, tu as appris :

  • Ce que sont les type hints et pourquoi les utiliser
  • Les annotations de base : int, str, float, bool, list, dict
  • Optional et Union (et la syntaxe | en Python 3.10+)
  • Les type hints pour les fonctions (paramètres et types de retour)
  • Les type hints pour les classes et les méthodes
  • Les techniques avancées : Callable, TypeAlias, Generic
  • Les Enums pour des valeurs sûres
  • Des outils comme mypy
  • Les bonnes pratiques des type hints

Point de contrôle

Avant de passer à la suite :

  • Tu comprends la différence entre les type hints et le comportement à l'exécution
  • Tu sais ajouter des type hints aux fonctions
  • Tu connais Optional et Union (et |)
  • Tu sais utiliser list[T], dict[K, V]
  • Tu comprends TypeAlias et Generic

Analogie Safari : les type hints sont comme une classification biologique précise - au lieu de « grand félin » on dit « Panthera leo, mâle, 5 ans, 190kg » - tout est clair !

Dans la prochaine leçon, Darwin t'apprendra les décorateurs - un outil puissant pour modifier les comportements des fonctions et des classes !

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. En Python, les type hints sont :

  2. 2. Comment annote-t-on le type de retour d'une fonction ?

Tâches pratiques dans le jeu

  • Tri vertical

    Range les éléments typiques d'une définition de classe :

  • Éditeur de code

    Crée une classe Animal avec des type hints : name: str, age: int, weight: float

  • Tri horizontal

    Range l'en-tête d'une méthode speak qui renvoie du texte (type hint str) :

Articles utiles