
Un type hint est une annotation qui indique le type attendu d'une variable, d'un paramètre ou d'une valeur de retour. Python reste un langage à typage dynamique : ces annotations ne sont pas vérifiées à l'exécution, mais elles documentent le code, alimentent l'autocomplétion de l'éditeur et permettent à un outil comme mypy d'attraper des bugs avant de lancer le programme. Ce guide couvre l'annotation des fonctions et variables, le typage des collections (list, dict), les unions (X | None), les alias, et la vérification avec mypy.
Il s'adresse aux développeurs qui veulent un code plus sûr et plus lisible, et à celles et ceux qui utilisent des outils modernes comme Pydantic, FastAPI, les dataclasses ou SQLAlchemy 2.0, tous bâtis sur les annotations de type. Les exemples ont été exécutés avec Python 3.12 et vérifiés avec mypy.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Annoter paramètres, valeurs de retour et variables.
- Typer les collections avec
list[int],dict[str, float]. - Exprimer une valeur optionnelle ou une union avec
X | None. - Créer des alias de type pour clarifier les signatures.
- Vérifier votre code avec mypy et comprendre ses limites.
Qu'est-ce qu'un type hint
Section intitulée « Qu'est-ce qu'un type hint »Un type hint (indice de type) est une annotation ajoutée au code pour préciser le type attendu. La syntaxe repose sur deux-points pour les variables et paramètres, et sur -> pour la valeur de retour d'une fonction.
def saluer(nom: str) -> str: return f"Bonjour {nom}"
age: int = 30Point essentiel : Python ignore ces annotations à l'exécution. Elles ne convertissent ni ne valident les valeurs. Un appel avec un mauvais type s'exécute quand même.
def double(x: int) -> int: return x * 2
double("oups") # aucune erreur : renvoie 'oupsoups'Les annotations sont donc un contrat, pas une contrainte : ce sont des outils externes (mypy, l'éditeur) qui les font respecter. C'est ce qui distingue le type hinting d'un vrai typage statique comme en Java.
Annoter fonctions et variables
Section intitulée « Annoter fonctions et variables »L'usage le plus courant est d'annoter les paramètres et la valeur de retour d'une fonction. C'est là que les annotations rendent le plus service : elles décrivent le contrat de la fonction d'un coup d'œil.
def moyenne(notes: list[float]) -> float: return sum(notes) / len(notes)
def est_majeur(age: int) -> bool: return age >= 18Une fonction qui ne renvoie rien s'annote avec None :
def afficher(message: str) -> None: print(message)Typer les collections
Section intitulée « Typer les collections »Depuis Python 3.9 (PEP 585), on annote les collections avec les types natifs directement suivis de crochets, sans rien importer :
notes: list[float] = [12.0, 15.5]ages: dict[str, int] = {"Alice": 30, "Bob": 25}coord: tuple[int, int] = (10, 20)tags: set[str] = {"prod", "web"}Les crochets précisent le type des éléments : list[float] est une liste de flottants, dict[str, int] associe des clés str à des valeurs int.
Valeurs optionnelles et unions
Section intitulée « Valeurs optionnelles et unions »Une valeur qui peut être absente (None) est très fréquente. Depuis Python 3.10 (PEP 604), on l'exprime avec l'opérateur | :
def chercher_utilisateur(nom: str) -> str | None: # renvoie l'email, ou None si l'utilisateur n'existe pas ...Le | sert plus largement à décrire une union de plusieurs types :
def normaliser(valeur: int | str) -> str: return str(valeur)Alias de type
Section intitulée « Alias de type »Quand un type devient long ou revient souvent, un alias clarifie les signatures. Depuis Python 3.12 (PEP 695), on le déclare avec le mot-clé type :
type Vecteur = list[float]type Coordonnees = dict[str, tuple[float, float]]
def norme(v: Vecteur) -> float: return sum(x * x for x in v) ** 0.5Sur une version antérieure à 3.12, un simple assignement fait aussi l'affaire : Vecteur = list[float].
Vérifier son code avec mypy
Section intitulée « Vérifier son code avec mypy »Les annotations ne servent vraiment que si un outil les contrôle. mypy est le vérificateur de type de référence : il analyse le code sans l'exécuter et signale les incompatibilités.
pip install mypymypy mon_projet/Sur un fichier où une variable reçoit le mauvais type, mypy le détecte avant l'exécution :
def double(x: int) -> int: return x * 2
resultat: str = double(21) # bug : double renvoie un interror: Incompatible types in assignment(expression has type "int", variable has type "str") [assignment]C'est tout l'intérêt : attraper une classe entière de bugs avant qu'ils n'atteignent la production. On intègre mypy en pre-commit et en CI/CD pour qu'il tourne à chaque commit. pyright est une alternative rapide, intégrée à Visual Studio Code.
Aller plus loin : TypedDict, Protocol et génériques
Section intitulée « Aller plus loin : TypedDict, Protocol et génériques »Pour des besoins plus précis, le module typing fournit des outils avancés.
Un TypedDict décrit la forme attendue d'un dictionnaire, pratique pour une réponse JSON ou une configuration :
from typing import TypedDict
class Utilisateur(TypedDict): nom: str age: int
u: Utilisateur = {"nom": "Alice", "age": 30}Un Protocol définit un typage structurel : n'importe quel objet possédant les bonnes méthodes est accepté, sans héritage explicite. C'est le duck typing, mais vérifié.
from typing import Protocol
class Fermable(Protocol): def close(self) -> None: ...
def liberer(ressource: Fermable) -> None: ressource.close() # tout objet avec close() convientEnfin, on écrit des fonctions génériques avec la syntaxe PEP 695 (Python 3.12), sans déclarer de TypeVar :
def premier[T](elements: list[T]) -> T: return elements[0]
premier([1, 2, 3]) # mypy sait que le résultat est un intpremier(["a", "b"]) # ... et ici un strPourquoi annoter son code
Section intitulée « Pourquoi annoter son code »Adopter les type hints demande un petit effort, vite rentabilisé :
- Moins de bugs : mypy attrape les erreurs de type avant l'exécution.
- Meilleure autocomplétion : l'éditeur connaît les types et propose les bonnes méthodes.
- Documentation vivante : la signature dit exactement ce que la fonction attend et renvoie.
- Écosystème moderne : Pydantic, FastAPI, les dataclasses et SQLAlchemy 2.0 s'appuient sur les annotations pour valider, sérialiser ou générer du code.
Inutile de tout annoter d'un coup : commencez par les signatures de fonctions publiques, là où le contrat compte le plus.
À retenir
Section intitulée « À retenir »- Un type hint annote le type attendu (
x: int,-> str) sans être vérifié à l'exécution. - La vérification se fait avec un outil externe (mypy, pyright), jamais par l'interpréteur.
- Depuis Python 3.9, on type les collections en natif :
list[int],dict[str, float]. - Depuis Python 3.10, une valeur optionnelle s'écrit
X | None(au lieu deOptional[X]). - Le mot-clé
type(3.12) crée des alias lisibles. - mypy attrape les erreurs de type avant l'exécution ; on l'intègre en pre-commit et en CI.
TypedDictetProtocolaffinent le typage des structures et des interfaces.- Les annotations sont la base de Pydantic, FastAPI, des dataclasses et de SQLAlchemy 2.0.
FAQ : les type hints en Python
Section intitulée « FAQ : les type hints en Python »def saluer(nom: str) -> str:
return f"Bonjour {nom}"
Ces annotations servent à trois choses :- documenter clairement les entrées et sorties,
- alimenter l'autocomplétion et les avertissements de l'éditeur,
- permettre à un outil comme mypy de détecter des erreurs avant l'exécution.
def double(x: int) -> int:
return x * 2
double("oups") # s'exécute quand même -> 'oupsoups'
La vérification se fait en amont, avec un outil externe comme mypy ou pyright, ou dans l'éditeur. Les annotations sont un contrat vérifié par des outils, pas une contrainte imposée par l'interpréteur.Des bibliothèques comme Pydantic ajoutent, elles, une validation au runtime en s'appuyant sur ces annotations.notes: list[float] = [12.0, 15.5]
ages: dict[str, int] = {"Alice": 30}
coord: tuple[int, int] = (10, 20)
tags: set[str] = {"prod", "web"}
Inutile d'importer List ou Dict depuis typing : c'était nécessaire avant 3.9, c'est aujourd'hui dépassé. Les crochets précisent le type des éléments contenus.| :def chercher(nom: str) -> str | None:
...
age: int | str = 30 # union : accepte les deux types
str | None signifie « une chaîne ou None ». Cette syntaxe remplace l'ancien Optional[str] du module typing, plus verbeux. Le | sert plus largement aux unions de plusieurs types.pip install mypy
mypy mon_projet/
Il analyse les annotations sans exécuter le code et signale les incompatibilités :bug.py:4: error: Incompatible types in assignment
(expression has type "int", variable has type "str")
On l'intègre ensuite en pre-commit et en CI/CD pour vérifier les types à chaque commit. pyright est une alternative rapide, intégrée à VS Code.typing pour des cas plus fins :TypedDictdécrit la forme d'un dictionnaire (utile pour du JSON, une config) :
from typing import TypedDict
class Utilisateur(TypedDict):
nom: str
age: int
Protocoldéfinit un typage structurel : tout objet ayant les bonnes méthodes convient, sans héritage :
from typing import Protocol
class Fermable(Protocol):
def close(self) -> None: ...
Les deux affinent les annotations pour des structures et des interfaces précises.Prochaines étapes
Section intitulée « Prochaines étapes »Les annotations prennent tout leur sens sur les fonctions, la POO et les outils qui s'en servent.