42. Introduction en douceur aux indications de type (facultatif)
Tout au long de ce livre, vous avez écrit du code Python sans préciser quels types de données vos variables contiennent ou quels types vos fonctions acceptent et renvoient. Python a très bien fonctionné ainsi : c’est un langage à typage dynamique (dynamically typed), ce qui signifie que les types sont déterminés à l’exécution, à mesure que votre programme s’exécute. Cette flexibilité est l’une des plus grandes forces de Python, car elle vous permet d’écrire du code rapidement et de manière expressive.
Cependant, à mesure que les programmes deviennent plus grands et plus complexes, cette flexibilité peut parfois rendre le code plus difficile à comprendre et à maintenir. Quand vous voyez une fonction comme def process_data(items):, vous pouvez vous demander : Quel type de données items contient-il ? Une liste de chaînes de caractères ? Un dictionnaire ? Quelque chose d’entièrement différent ?
Les indications de type (type hints) (aussi appelées annotations de type (type annotations)) offrent un moyen de documenter les types attendus dans votre code. Ce sont des ajouts facultatifs à Python qui peuvent rendre votre code plus clair, aider à détecter des erreurs plus tôt et activer de puissantes fonctionnalités d’IDE — le tout sans changer la manière dont Python exécute réellement votre code.
Ce chapitre introduit les indications de type en douceur, en vous montrant ce qu’elles sont, pourquoi elles existent et comment les utiliser efficacement. Comme les indications de type sont facultatives et n’affectent pas la façon dont Python exécute votre code, l’ensemble de ce chapitre est marqué comme facultatif. Vous pouvez écrire d’excellents programmes Python sans jamais utiliser d’indications de type. Mais les comprendre vous aidera à lire du code Python moderne et à décider quand elles pourraient être utiles à vos propres projets.
42.1) Pourquoi les indications de type ont été ajoutées à Python
Python a été conçu comme un langage à typage dynamique dès le départ. Pendant des décennies, les programmeurs Python ont écrit du code sans aucune information de type, et cela a merveilleusement fonctionné pour d’innombrables projets. Alors pourquoi les indications de type ont-elles été ajoutées à Python en 2015 (avec Python 3.5) ?
Le défi des grandes bases de code
À mesure que Python est devenu plus populaire pour des applications à grande échelle, les équipes ont rencontré des défis :
# Dans une grande base de code, qu'attend et que renvoie cette fonction ?
def calculate_discount(customer, items, code):
# ... 50 lines of code ...
return resultSans lire tout le corps de la fonction ou sa documentation, vous ne pouvez pas savoir :
customerest-il un dictionnaire, un objet personnalisé, ou autre chose ?itemsest-il une liste, un tuple, ou un set ?- Quel est le type de
code— une chaîne, un entier ? - Que renvoie la fonction — un nombre, un dictionnaire, ou peut-être
None?
Dans les petits programmes, cette ambiguïté est gérable. Vous pouvez facilement regarder comment la fonction est utilisée ailleurs. Mais dans une base de code avec des milliers de fonctions réparties sur des dizaines de fichiers, cela devient difficile.
La solution : des indications de type facultatives
Les créateurs de Python ont décidé d’ajouter un système facultatif pour documenter les types. Le mot-clé est « facultatif » : les indications de type sont entièrement volontaires. Vous pouvez les utiliser quand elles vous aident, les ignorer quand elles ne vous aident pas, et mélanger librement du code annoté et non annoté.
Voici un exemple simple pour montrer la syntaxe de base :
# Sans indications de type
def add(a, b):
return a + b
# Avec indications de type
def add(a: int, b: int) -> int:
return a + bLa syntaxe est simple :
- Deux-points (
:) après un paramètre indique le type qu’il devrait avoir :a: int - Flèche (
->) avant les deux-points indique le type renvoyé par la fonction :-> int
Voyons maintenant cela avec notre exemple précédent :
def calculate_discount(customer: dict, items: list, code: str) -> float:
# ... 50 lines of code ...
return resultMaintenant, c’est immédiatement clair : customer est un dictionnaire, items est une liste, code est une chaîne, et la fonction renvoie un float.
Ne vous inquiétez pas si cette syntaxe vous semble peu familière — nous l’explorerons en détail dans les sections 42.3 à 42.6. Pour l’instant, remarquez simplement à quel point on peut voir d’un coup d’œil ce qu’une fonction attend et ce qu’elle renvoie.
Avec ou sans indications de type, la fonction fonctionne exactement de la même façon — Python ne vérifie pas ces types à l’exécution. (Nous explorerons ce point important en détail dans la section 42.2)
Une approche progressive et pragmatique
Le système d’indications de type de Python a été conçu pour être :
- Facultatif : vous n’êtes jamais obligé d’utiliser des indications de type
- Progressif : vous pouvez ajouter des indications à certaines parties de votre code et pas à d’autres
- Non intrusif : les indications ne changent pas la manière dont Python exécute votre code
- Compatible avec les outils : des outils externes peuvent vérifier les indications, mais Python lui-même les ignore à l’exécution
Cette approche pragmatique permet à Python de rester flexible tout en offrant des avantages à ceux qui les veulent.
42.2) La règle d’or : aucune application à l’exécution
La chose la plus importante à comprendre à propos des indications de type est la suivante : Python n’applique pas les indications de type à l’exécution. Elles sont purement informatives. Voyons ce que cette réalité surprenante signifie en pratique.
Les indications de type n’empêchent pas les types incorrects
Considérez cette fonction avec des indications de type :
def greet(name: str) -> str:
return f"Hello, {name}!"
# Cela fonctionne très bien, même si 42 n'est pas une chaîne
result = greet(42)
print(result) # Output: Hello, 42!L’indication de type dit clairement que name devrait être une chaîne, mais Python accepte volontiers l’entier 42 et exécute la fonction. Python ne vérifie pas l’indication de type — il utilise simplement la valeur que vous fournissez.
C’est fondamentalement différent de langages comme Java ou C++, où le compilateur vérifie les types avant d’exécuter votre code et refuse de s’exécuter s’il y a une incompatibilité de types. L’approche de Python est plus permissive : il vous fait confiance pour fournir les bons types, mais ne vous y oblige pas.
Le problème : les risques du typage dynamique restent
Voici le vrai défi : même avec des indications de type, le typage dynamique de Python signifie que vous pouvez toujours faire des erreurs de type qui n’apparaissent qu’à l’exécution :
def calculate_total(prices: list) -> float:
"""Calculer la somme des prix."""
return sum(prices)
# Cela fonctionne très bien
print(calculate_total([10.99, 5.50, 3.25])) # Output: 19.74
# Mais cela échoue à l'exécution !
print(calculate_total("not a list")) # TypeError: 'str' object is not iterableL’indication de type dit clairement que prices devrait être une liste, mais Python ne vous empêche pas de passer une chaîne. L’erreur n’apparaît que lorsque le code s’exécute réellement et essaie d’utiliser sum() sur la chaîne.
C’est frustrant ! Nous avons ajouté des indications de type pour attraper ces problèmes, mais les risques du typage dynamique sont toujours là. Les erreurs de type peuvent se cacher dans votre code jusqu’à l’exécution, et potentiellement apparaître en production quand un utilisateur fait quelque chose d’inattendu.
Donc, si les indications de type n’empêchent pas les erreurs à l’exécution, quel est l’intérêt de les utiliser ?
Alors à quoi servent les indications de type ?
Les indications de type ne changent peut-être pas le comportement à l’exécution de Python, mais elles servent un objectif crucial : elles fournissent des informations aux humains et aux outils, pas à Python lui-même :
- Documentation : elles vous disent quels types une fonction attend et renvoie
- Support IDE : votre éditeur peut utiliser les indications pour proposer de l’autocomplétion et afficher des avertissements
- Analyse statique : des outils externes (comme mypy) peuvent vérifier votre code pour y trouver des erreurs de type avant que vous ne l’exécutiez
- Compréhension du code : elles rendent les grandes bases de code plus faciles à lire et à maintenir
Considérez les indications de type comme des commentaires que les outils peuvent comprendre. Elles ne changent pas la façon dont Python s’exécute, mais elles vous aident à écrire un meilleur code.
Mais comment cela nous aide-t-il réellement à attraper ces erreurs d’exécution que nous venons de voir ?
La solution : indications de type + support IDE
C’est ici que les indications de type brillent vraiment. Même si Python ne les applique pas à l’exécution, votre IDE peut repérer des erreurs avant même que vous n’exécutiez le code :
def add_numbers(a: int, b: int) -> int:
"""Additionner deux nombres."""
return a + b
# Votre IDE affichera un avertissement ici (avant que vous n'exécutiez le code)
result = add_numbers("Hello", "World") # IDE: Warning - expected int, got strVotre éditeur de code voit les indications de type et peut vous avertir des incompatibilités de types pendant que vous tapez, bien avant que vous n’exécutiez le code. Cela attrape de nombreux bugs pendant le développement plutôt qu’en production.
Le développement Python moderne fonctionne généralement ainsi :
- Vous écrivez du code avec des indications de type
- Votre IDE affiche des avertissements lorsque les types ne correspondent pas
- Vous corrigez les problèmes avant d’exécuter le code
- Les erreurs d’exécution dues à des incompatibilités de types deviennent beaucoup plus rares
L’indication de type n’empêche pas l’erreur à l’exécution, mais votre IDE l’utilise pour vous empêcher d’écrire du code bogué dès le départ !
Le meilleur des deux mondes
Les indications de type donnent à Python le meilleur des deux mondes : détecter la plupart des erreurs tôt tout en conservant la flexibilité :
Sécurité en développement : votre IDE et les vérificateurs de types attrapent la plupart des erreurs de type pendant le développement, donc vous trouvez les bugs tôt.
def process(data: list) -> list:
return [x * 2 for x in data]
# Si vous passez accidentellement une chaîne :
process("hello") # IDE warns: expected list, got str
# Vous le corrigez avant d'exécuter le code !Flexibilité à l’exécution : Python exécute toujours du code avec des incompatibilités de types, ce qui peut être utile pour prototyper rapidement ou quand vous voulez intentionnellement accepter plusieurs types.
def add_numbers(a: int, b: int) -> int:
return a + b
# Python exécutera ceci, même si les types ne correspondent pas
print(add_numbers(5.5, 3.2)) # Output: 8.7 (works!)
print(add_numbers("Hi", " there")) # Output: Hi there (also works!)Cette flexibilité signifie que vous n’êtes pas enfermé dans un système de types rigide. Quand vous avez besoin d’enfreindre les règles (pour des tests, du prototypage ou des cas d’usage légitimes), Python vous le permet. Mais quand vous écrivez du code de production, votre IDE vous garde en sécurité.
Rappelez-vous la règle d’or : les indications de type ne changent pas le comportement à l’exécution de Python — elles vous donnent simplement, à vous et à vos outils, les informations nécessaires pour détecter les problèmes tôt. Vous devez toujours faire attention, mais désormais vous avez de puissants alliés qui vous protègent.
42.3) Annoter les fonctions : paramètres et valeurs de retour
L’usage le plus courant des indications de type est d’annoter les paramètres des fonctions et leurs valeurs de retour. Cela indique aux lecteurs (et aux outils) quels types une fonction attend et produit. Commençons par le cas le plus simple et construisons progressivement.
Les bases : annotations de paramètres
Pour ajouter une indication de type à un paramètre, placez deux-points après le nom du paramètre, suivis du type :
def greet(name: str):
"""Saluer une personne par son nom."""
return f"Hello, {name}!"
# Utilisation
message = greet("Alice")
print(message) # Output: Hello, Alice!La syntaxe name: str signifie « le paramètre name devrait être une chaîne ». Vous pouvez ajouter des indications de type à plusieurs paramètres :
def calculate_area(width: float, height: float):
"""Calculer l'aire d'un rectangle."""
return width * height
# Utilisation
area = calculate_area(5.0, 3.0)
print(area) # Output: 15.0Ici, width et height sont tous deux annotés comme float. La fonction fonctionne de la même manière qu’avant — les indications de type ne changent pas le comportement — mais maintenant votre IDE sait quels types attendre.
Ajouter des annotations de type de retour
Pour spécifier le type qu’une fonction renvoie, ajoutez -> type après la liste des paramètres et avant les deux-points :
def get_full_name(first: str, last: str) -> str:
"""Combiner le prénom et le nom."""
return f"{first} {last}"
# Utilisation
name = get_full_name("John", "Doe")
print(name) # Output: John DoeLe -> str signifie « cette fonction renvoie une chaîne ». Les annotations de type de retour sont particulièrement utiles lorsque le type de retour n’est pas évident à partir du nom de la fonction :
def is_adult(age: int) -> bool:
"""Vérifier si quelqu'un est un adulte (18 ans ou plus)."""
return age >= 18
# Utilisation
adult = is_adult(25)
print(adult) # Output: TrueSans regarder l’implémentation, vous savez immédiatement que cette fonction renvoie une valeur booléenne.
Tout rassembler : une fonction complète
La plupart des fonctions auront à la fois des annotations de paramètres et de type de retour. Voici à quoi ressemble une fonction entièrement annotée :
def calculate_discount(price: float, discount_percent: float) -> float:
"""Calculer le prix après réduction."""
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Utilisation
original_price = 100.0
discount = 20.0
final_price = calculate_discount(original_price, discount)
print(f"Final price: ${final_price:.2f}") # Output: Final price: $80.00La signature de cette fonction vous dit tout ce que vous devez savoir :
- Elle prend deux paramètres
float:priceetdiscount_percent - Elle renvoie une valeur
float - Vous n’avez pas besoin de lire l’implémentation pour comprendre comment utiliser cette fonction
Voyons un autre exemple avec des types différents :
def repeat_message(message: str, times: int) -> str:
"""Répéter un message un nombre de fois spécifié."""
return message * times
# Utilisation
repeated = repeat_message("Hello! ", 3)
print(repeated) # Output: Hello! Hello! Hello! Les indications de type montrent clairement que vous passez une chaîne et un entier, et que vous récupérez une chaîne.
Travailler avec des valeurs par défaut
Lorsqu’un paramètre a une valeur par défaut, placez l’indication de type entre le nom du paramètre et la valeur par défaut :
def create_greeting(name: str, formal: bool = False) -> str:
"""Créer un message de salutation."""
if formal:
return f"Good day, {name}."
return f"Hi, {name}!"
# Utilisation
print(create_greeting("Alice")) # Output: Hi, Alice!
print(create_greeting("Bob", formal=True)) # Output: Good day, Bob.La syntaxe formal: bool = False signifie « formal est un booléen avec une valeur par défaut False ».
Vous pouvez avoir plusieurs paramètres avec des valeurs par défaut, tous annotés :
def format_price(amount: float, currency: str = "USD", decimals: int = 2) -> str:
"""Mettre en forme un prix avec un symbole de devise."""
if currency == "USD":
symbol = "$"
elif currency == "EUR":
symbol = "€"
else:
symbol = currency
return f"{symbol}{amount:.{decimals}f}"
# Utilisation
print(format_price(99.99)) # Output: $99.99
print(format_price(99.99, "EUR")) # Output: €99.99
print(format_price(99.995, "USD", 3)) # Output: $99.995Chaque paramètre montre clairement son type et sa valeur par défaut, ce qui rend la fonction facile à comprendre et à utiliser.
Cas particulier : fonctions qui ne renvoient pas de valeurs
Certaines fonctions ne font qu’exécuter des actions (comme afficher ou écrire dans un fichier) sans renvoyer de valeur. Pour indiquer clairement que ces fonctions ne renvoient rien, utilisez -> None :
def print_report(title: str, data: list) -> None:
"""Afficher un rapport mis en forme."""
print(f"=== {title} ===")
for item in data:
print(f" - {item}")
# Pas d'instruction return, donc renvoie None implicitement
# Utilisation
print_report("Sales Data", [100, 150, 200])Output:
=== Sales Data ===
- 100
- 150
- 200L’annotation -> None indique explicitement que cette fonction ne renvoie pas de valeur significative.
Pourquoi utiliser -> None ?
- Clarté : cela rend votre intention explicite — cette fonction sert à faire des actions, pas à produire des résultats
- Support IDE : votre IDE peut vous avertir si vous essayez accidentellement d’utiliser la valeur de retour
42.4) Annotations simples de variables
Même si les indications de type sont le plus souvent utilisées avec les fonctions, vous pouvez aussi annoter des variables. Voyons comment cela fonctionne et quand c’est réellement utile.
Syntaxe de base des annotations de variables
Pour annoter une variable, utilisez la même syntaxe avec deux-points que pour les paramètres de fonctions :
# Annoter des variables
name: str = "Alice"
age: int = 30
height: float = 5.7
is_student: bool = True
print(f"{name} is {age} years old") # Output: Alice is 30 years oldLa syntaxe name: str = "Alice" signifie « la variable name est une chaîne et a la valeur 'Alice' ». L’annotation ne change pas le fonctionnement de la variable — elle est purement informative.
Les annotations de variables sont souvent ignorées
En pratique, les annotations de variables sont rarement utilisées. La raison est simple : Python peut déduire le type à partir de la valeur, donc les annotations sont généralement redondantes :
# Ces annotations sont inutiles
name: str = "Alice" # Évidemment une chaîne
count: int = 0 # Évidemment un int
prices: list = [10.99, 5.50] # Évidemment une liste
settings: dict = {} # Évidemment un dict
# Écrivez simplement ceci à la place
name = "Alice"
count = 0
prices = [10.99, 5.50]
settings = {}Quand vous écrivez name = "Alice", vous et votre IDE savez immédiatement que c’est une chaîne. L’annotation n’ajoute aucune information utile.
Dans du code Python réel, vous verrez rarement des annotations de variables. C’est normal et attendu. Les annotations de fonctions sont bien plus importantes et courantes.
Le seul cas utile : déclarer des variables avant l’affectation
Il y a une situation où les annotations de variables sont réellement utiles : quand vous devez déclarer une variable avant de lui assigner une valeur.
def calculate_statistics(numbers: list) -> dict:
"""Calculer des statistiques de base à partir d'une liste de nombres."""
# Déclarer des variables avant de les utiliser
total: float
count: int
average: float
# Maintenant assigner des valeurs
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0.0
return {
"total": total,
"count": count,
"average": average
}
# Utilisation
result = calculate_statistics([10, 20, 30, 40])
print(f"Average: {result['average']}") # Output: Average: 25.0Sans annotations, vous ne pouvez pas déclarer une variable sans lui assigner aussi une valeur. Les annotations vous permettent de spécifier les types à l’avance, ce qui peut rendre la structure du code plus claire.
C’est le principal cas d’usage pratique des annotations de variables.
Rappelez-vous : les variables peuvent être réassignées à différents types
Même avec une annotation de type, vous pouvez réassigner une variable à un type différent :
# Commencer avec une chaîne
value: str = "hello"
print(value) # Output: hello
# Réassigner à un type différent - Python l'autorise
value = 42
print(value) # Output: 42
# Un autre changement de type - toujours autorisé
value = [1, 2, 3]
print(value) # Output: [1, 2, 3]Votre IDE ou un vérificateur de types statique vous avertira de ces changements de type, mais Python lui-même ne les empêche pas. Les indications de type vous guident vers la cohérence, mais ne l’imposent pas à l’exécution.
42.5) Gérer None : types optionnels et l’opérateur |
L’un des schémas les plus courants en Python est une fonction qui peut renvoyer une valeur ou renvoyer None. Par exemple, rechercher un élément peut réussir (en renvoyant l’élément) ou échouer (en renvoyant None). Les indications de type offrent des moyens clairs d’exprimer ce schéma.
Le problème : les fonctions qui peuvent renvoyer None
Considérez cette fonction qui recherche un utilisateur :
def find_user_by_email(email: str) -> dict:
"""Trouver un utilisateur par adresse e-mail."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None # Incompatibilité de type ! Cela contredit l'indication -> dict
# Utilisation
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
else:
print("User not found")L’indication de type -> dict est trompeuse, car la fonction peut renvoyer None. Un vérificateur de types statique vous avertirait que renvoyer None ne correspond pas au type de retour déclaré dict.
Solution : utiliser l’opérateur | pour les types optionnels
Python 3.10 a introduit l’opérateur | pour les indications de type, qui signifie « ou ». Vous pouvez l’utiliser pour indiquer qu’une fonction peut renvoyer un type ou un autre :
def find_user_by_email(email: str) -> dict | None:
"""Trouver un utilisateur par adresse e-mail. Renvoie None si introuvable."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None
# Utilisation
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
missing = find_user_by_email("charlie@example.com")
if missing is None:
print("User not found") # Output: User not foundL’indication de type -> dict | None signifie « cette fonction renvoie soit un dictionnaire, soit None ». Cela décrit précisément le comportement de la fonction.
Remarque : dans du code Python plus ancien (avant 3.10), vous pouvez voir Optional[dict] depuis le module typing au lieu de dict | None. Cela signifie la même chose, mais | est la syntaxe moderne et préférée.
Utiliser | avec plusieurs types
Vous pouvez utiliser | pour indiquer plus de deux types possibles :
def parse_value(text: str) -> int | float | None:
"""Convertir une chaîne en nombre. Renvoie None si l'analyse échoue."""
try:
# Essayer d'analyser comme un entier d'abord
if '.' not in text:
return int(text)
# Sinon analyser comme un float
return float(text)
except ValueError:
return None
# Utilisation
print(parse_value("42")) # Output: 42 (int)
print(parse_value("3.14")) # Output: 3.14 (float)
print(parse_value("invalid")) # Output: NoneL’indication de type -> int | float | None signifie que la fonction peut renvoyer un entier, un float, ou None.
Vérifier None : bonnes pratiques
Lorsqu’une fonction peut renvoyer None, vérifiez toujours None avant d’utiliser le résultat. Sinon, vous risquez des erreurs en essayant d’utiliser None comme si c’était le type attendu :
def get_user_age(user_id: int) -> int | None:
"""Obtenir l'âge de l'utilisateur. Renvoie None si l'utilisateur est introuvable."""
users = {1: 25, 2: 30, 3: 35}
return users.get(user_id)
# Vérifiez toujours None avant d'utiliser la valeur
age = get_user_age(1)
if age is not None:
print(f"User is {age} years old") # Output: User is 25 years old
if age >= 18:
print("User is an adult") # Output: User is an adult
else:
print("User not found")
# Pour des utilisateurs inexistants
age = get_user_age(999)
if age is None:
print("User not found") # Output: User not foundLa clé est d’utiliser if age is not None: ou if age is None: pour vérifier explicitement avant d’utiliser la valeur.
Paramètres optionnels avec | None
Vous pouvez aussi utiliser | avec les paramètres, souvent combiné avec des valeurs par défaut :
def format_name(first: str, middle: str | None = None, last: str = "") -> str:
"""Mettre en forme un nom complet. Le deuxième prénom est facultatif."""
if middle and last:
return f"{first} {middle} {last}"
elif last:
return f"{first} {last}"
return first
# Utilisation
print(format_name("John", "Q", "Doe")) # Output: John Q Doe
print(format_name("Jane", None, "Smith")) # Output: Jane Smith
print(format_name("Prince")) # Output: PrinceL’indication de type middle: str | None = None indique que middle peut être une chaîne ou None, avec None comme valeur par défaut. C’est un schéma courant pour les paramètres optionnels.
42.6) Lire des indications de type courantes : list, dict, tuple
Lorsque vous lisez du code Python écrit par d’autres, vous rencontrerez des indications de type pour des collections comme les listes, les dictionnaires et les tuples. Python moderne propose des moyens clairs de spécifier non seulement que quelque chose est une liste, mais aussi quel type d’éléments la liste contient.
Remarque : la syntaxe montrée ici (list[int], dict[str, int], etc.) fonctionne en Python 3.9+. Dans du code plus ancien, vous pouvez voir List[int] et Dict[str, int] (avec une majuscule) depuis le module typing — elles fonctionnent de la même manière.
Indications de type de collections de base
Les indications de type de collections les plus simples se contentent de spécifier le type de collection :
def print_items(items: list) -> None:
"""Afficher tous les éléments d'une liste."""
for item in items:
print(item)
def get_user_settings() -> dict:
"""Obtenir les paramètres utilisateur sous forme de dictionnaire."""
return {"theme": "dark", "notifications": True}
def get_position() -> tuple:
"""Obtenir la position x, y."""
return (10, 20)Ces indications vous disent le type de collection, mais pas ce qu’il y a à l’intérieur.
Listes : spécifier les types des éléments
Pour spécifier quel type d’éléments une liste contient, utilisez des crochets :
def calculate_total(prices: list[float]) -> float:
"""Calculer le total de tous les prix."""
return sum(prices)
# Utilisation
total = calculate_total([10.99, 5.50, 3.25])
print(f"Total: ${total:.2f}") # Output: Total: $19.74L’indication de type list[float] signifie « une liste contenant des floats ». C’est plus informatif que simplement list.
Voici un autre exemple avec des chaînes :
def format_names(names: list[str]) -> str:
"""Mettre en forme une liste de noms en une chaîne séparée par des virgules."""
return ", ".join(names)
# Utilisation
students = ["Alice", "Bob", "Charlie"]
print(format_names(students)) # Output: Alice, Bob, CharlieL’indication de type list[str] signifie « une liste contenant des chaînes ».
Dictionnaires : spécifier les types des clés et des valeurs
Pour les dictionnaires, spécifiez à la fois le type des clés et celui des valeurs :
def get_student_grades() -> dict[str, int]:
"""Obtenir les noms des étudiants associés à leurs notes."""
return {
"Alice": 95,
"Bob": 87,
"Charlie": 92
}
# Utilisation
grades = get_student_grades()
for name, grade in grades.items():
print(f"{name}: {grade}")Output:
Alice: 95
Bob: 87
Charlie: 92L’indication de type dict[str, int] signifie « un dictionnaire avec des clés chaînes et des valeurs entières ».
Voici un exemple où les valeurs peuvent être de plusieurs types :
def get_user_data(user_id: int) -> dict[str, str | int]:
"""Obtenir les données utilisateur. Les valeurs peuvent être des chaînes ou des entiers."""
return {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"id": 12345
}
# Utilisation
user = get_user_data(1)
print(f"{user['name']} is {user['age']} years old") # Output: Alice is 30 years oldL’indication de type dict[str, str | int] signifie « un dictionnaire avec des clés chaînes et des valeurs qui sont soit des chaînes soit des entiers ».
Tuples : longueur fixe et longueur variable
Les tuples sont différents des listes parce qu’ils ont souvent une structure fixe. Vous pouvez spécifier le type de chaque position :
def get_user_info(user_id: int) -> tuple[str, int, bool]:
"""
Obtenir des informations utilisateur sous forme de tuple.
Renvoie : (name, age, is_active)
"""
return ("Alice", 30, True)
# Utilisation
name, age, active = get_user_info(1)
print(f"{name}, {age}, active: {active}") # Output: Alice, 30, active: TrueL’indication de type tuple[str, int, bool] signifie « un tuple avec exactement trois éléments : une chaîne, un entier et un booléen, dans cet ordre ».
Pour des tuples de longueur variable avec des éléments du même type, utilisez des points de suspension (...) :
# Tuple à longueur fixe : exactement 2 floats
def get_2d_point() -> tuple[float, float]:
"""Obtenir des coordonnées 2D (x, y)."""
return (10.5, 20.3)
# Tuple à longueur variable : n'importe quel nombre de floats
def get_coordinates() -> tuple[float, ...]:
"""Obtenir des coordonnées. Peut être 2D, 3D, ou de n'importe quelle dimension."""
return (10.5, 20.3, 15.7) # 3D dans ce cas
# Utilisation
point = get_2d_point()
coords = get_coordinates()
print(f"2D point: {point}") # Output: 2D point: (10.5, 20.3)
print(f"Coordinates: {coords}") # Output: Coordinates: (10.5, 20.3, 15.7)L’indication de type tuple[float, ...] signifie « un tuple contenant n’importe quel nombre de floats ». Le ... signifie « n’importe quel nombre de ce type ».
Collections imbriquées
Vous pouvez imbriquer des indications de type pour des structures de données complexes. Commençons par un exemple simple :
def get_scores_by_student() -> dict[str, list[int]]:
"""Obtenir les scores aux tests pour chaque étudiant."""
return {
"Alice": [95, 87, 92],
"Bob": [88, 91, 85],
"Charlie": [90, 88, 94]
}
# Utilisation
scores = get_scores_by_student()
for name, tests in scores.items():
average = sum(tests) / len(tests)
print(f"{name}: {average:.1f}")Output:
Alice: 91.3
Bob: 88.0
Charlie: 90.7L’indication de type dict[str, list[int]] signifie « un dictionnaire avec des clés chaînes et des valeurs listes d’entiers ».
Voici un exemple plus complexe :
def get_student_records() -> list[dict[str, str | int]]:
"""Obtenir une liste d'enregistrements d'étudiants."""
return [
{"name": "Alice", "age": 20, "major": "CS"},
{"name": "Bob", "age": 21, "major": "Math"},
{"name": "Charlie", "age": 19, "major": "Physics"}
]
# Utilisation
students = get_student_records()
for student in students:
print(f"{student['name']}, {student['age']}, {student['major']}")Output:
Alice, 20, CS
Bob, 21, Math
Charlie, 19, PhysicsL’indication de type list[dict[str, str | int]] signifie « une liste de dictionnaires, où chaque dictionnaire a des clés chaînes et des valeurs qui sont soit des chaînes soit des entiers ».
Lire les indications de type : un aide-mémoire rapide
Quand vous rencontrez des indications de type dans du code, voici comment les lire :
Collections :
list[int]- « une liste d’entiers »dict[str, float]- « un dictionnaire avec des clés chaînes et des valeurs float »tuple[str, int]- « un tuple avec exactement deux éléments : une chaîne, puis un entier »tuple[float, ...]- « un tuple contenant n’importe quel nombre de floats »
Optionnel et types multiples :
int | None- « un entier ou None »str | int | float- « une chaîne, un entier ou un float »
Imbriqué :
list[dict[str, int]]- « une liste de dictionnaires (chaque dict a des clés chaînes et des valeurs entières) »dict[str, list[float]]- « un dictionnaire avec des clés chaînes et des valeurs listes de floats »
Remarque : dans du code plus ancien (Python < 3.10), vous pouvez voir Union[int, str] au lieu de int | str, ou Optional[int] au lieu de int | None. Cela signifie la même chose.