Définition
Une annotation de type est une promesse. Rien dans le langage ne vérifie qu'elle est tenue : écrire total: float n'engage à rien de plus qu'un commentaire, tant que personne ne la relit à la lumière de ce que le code fait vraiment de la valeur. C'est ce vide que mypy comble. Il lit le code sans jamais l'exécuter, compare ce qu'une annotation de type annonce à l'usage qui en est fait plus loin, et signale chaque endroit où les deux ne concordent pas, sans rien réécrire ni changer au comportement du programme. Il s'installe avec pip et se lance en ligne de commande.
python -m mypy mon_projet/Sans lui, l'incohérence attend son heure : elle ne se manifeste qu'au moment où une opération échoue pour son propre compte, souvent bien plus loin dans le programme, sous la forme d'une TypeError. mypy avance ce moment de plusieurs heures, parfois de plusieurs semaines, et le ramène à l'endroit exact où l'erreur a été écrite.
# mypy compare l'annotation "float" à l'appel réel juste en dessous
def prix_unitaire(total: float, quantite: int) -> float:
return total / quantite
prix_unitaire("120", 4)
# error: Argument 1 has incompatible type "str"; expected "float"Le problème qu'il résout vraiment
Ce n'est pas la faute de l'exemple ci-dessus qu'un vérificateur passe le plus de temps à traquer sur du code réel : une chaîne à la place d'un nombre se voit au premier essai, sans outil pour la signaler. Ce qui échappe à l'œil, en revanche, c'est la valeur absente qui voyage sur plusieurs fonctions avant d'échouer. Une fonction capable de renvoyer None, dix endroits qui l'appellent, et un seul qui oublie de vérifier avant de se servir du résultat.
# dict | None annonce que le résultat peut manquer ; l'appel ci-dessous n'en tient pas compte
def chercher_client(identifiant: int) -> dict | None:
return base.lire(identifiant)
client = chercher_client(12)
print(client["nom"])
# error: Value of type "dict | None" is not indexableVoilà l'essentiel de ce qu'un vérificateur remonte sur du code qui tourne déjà en production : pas des types exotiques, mais des None jamais traités, un attribut renommé six mois plus tôt, un paramètre retiré d'une fonction dont un appelant lointain se sert toujours. Autant de bugs qui se seraient annoncés plus tard par une TypeError ou une AttributeError, à l'heure la plus mal choisie.
Ce qu'il ne voit pas
Un vérificateur ne connaît que ce qui est écrit noir sur blanc. Sur un projet dépourvu d'annotations, mypy affiche « Success: no issues found », et cette phrase ne dit rien de rassurant : il n'a rien vérifié, faute de promesse à confronter.
Lire ce message comme un feu vert est l'erreur la plus coûteuse qu'on puisse commettre avec mypy : sur un projet sans annotation, il ne prouve rien du tout, et le bug qu'on croyait écarté attend simplement son tour.
L'outil est tout aussi aveugle à ce qui entre depuis l'extérieur du programme. Un fichier JSON, une réponse d'API ou une ligne de base de données arrivent sans type connu, et l'annotation posée dessus est crue sur parole plutôt que vérifiée. Confirmer la forme réelle des données à leur arrivée revient à une bibliothèque de validation comme Pydantic, pas à mypy. Il raisonne enfin sur des déclarations et non sur des valeurs : une fonction qui renvoie un prix faux mais correctement typé passe sans une remarque.
Chaque défaut a son outil, et les confondre fait chercher au mauvais endroit :
| Le défaut | L'outil qui le voit |
|---|---|
| Un import inutilisé, une variable jamais relue | ruff |
| Des guillemets et des retours à la ligne qui changent d'un fichier à l'autre | black |
Un None passé là où une chaîne est attendue | mypy |
| Un calcul juste sur le papier et faux sur les vraies données | pytest |
| Un champ absent du JSON reçu ce matin | Pydantic |
L'erreur classique des premiers jours
Elle consiste à lancer le mode strict sur un projet qui existe depuis des années. Le terminal affiche neuf cents erreurs d'un coup, dont l'immense majorité dit seulement que telle fonction n'est pas annotée, et l'outil est désinstallé dans la demi-heure. Aucun de ces messages ne désigne un bug : ils décrivent l'absence d'annotations, ce qui était déjà connu avant de taper la commande.
La méthode qui tient sur la durée fait l'inverse. Un fichier à la fois, celui qu'on modifie de toute façon pour une autre raison, annoté au passage, et le mode strict réservé aux nouveaux modules, activé dossier par dossier dans la configuration du projet.
python -m mypy paiements/facturation.py
python -m mypy --strict paiements/nouveau_module.pyUn second piège guette, le même que pour n'importe quel paquet Python : installé en dehors de l'environnement virtuel du projet, le vérificateur ne voit pas les bibliothèques qui s'y trouvent et réclame des types qu'il ne trouvera jamais. La forme python -m mypy, plutôt que mypy tout court, écarte ce risque. Quant au message annonçant des types manquants pour telle bibliothèque, il demande d'installer un paquet séparé, souvent nommé types-quelque-chose, et non de poser un commentaire d'ignorance à la hâte.
mypy, Pyright, ou rien du tout
L'alternative directe s'appelle Pyright, et beaucoup l'utilisent déjà sans le savoir : c'est le moteur qui souligne leur code dans Visual Studio Code. Les deux outils font le même travail de fond, avec des équilibres différents. Le tableau résume ce qui les sépare :
| Critère | Ce qui les sépare |
|---|---|
| Vitesse | Pyright reste nettement plus rapide sur un gros projet |
| Sévérité par défaut | Pyright déduit et signale davantage, mypy attend des annotations explicites |
| Moment du retour | Pyright souligne pendant la frappe, mypy se lance à la demande |
| Écosystème | mypy est l'implémentation de référence des PEP sur les types |
Le vrai conseil tient en une ligne : choisissez-en un seul pour toute l'équipe, sans quoi elle finit par débattre de messages contradictoires sur les cas limites au lieu de corriger du code. Il existe aussi des projets où aucun des deux ne se justifie : un script de quarante lignes qu'on jette après usage, un carnet d'analyse jetable. Le bénéfice vient de la durée de vie du code et du nombre de mains qui y touchent, pas de la taille du fichier. Sur du code très dynamique, qui repose franchement sur le duck typing, annoter devient même pénible à écrire pour ce que ça rapporte.
Questions fréquentes
Faut-il annoter tout le projet avant que mypy serve à quelque chose ?
Non, et attendre d'avoir tout annoté reste le meilleur moyen de ne jamais commencer. Un projet annoté seulement sur ses fonctions les plus appelées rapporte déjà, puisque les erreurs de type circulent le long de ces fonctions-là. Une réserve à connaître tout de même : par défaut, il ignore le corps des fonctions non annotées, donc tant qu'une signature reste nue, rien ne sera dit sur ce qui se passe à l'intérieur.
Est-ce que cela remplace les tests ?
Non, les deux ne cherchent pas la même chose. Un vérificateur prouve que les pièces s'emboîtent, un test vérifie que le résultat obtenu est le bon. Une fonction de facturation qui renvoie un montant faux mais du bon type passe sans une remarque et échoue au premier test sérieux : aucun des deux ne couvre l'angle mort de l'autre.
Que faire quand le vérificateur se trompe ?
Cela arrive, surtout autour des bibliothèques mal typées. La sortie propre consiste à poser un commentaire d'ignorance sur la seule ligne concernée, avec le code d'erreur précis entre crochets et une phrase expliquant pourquoi. Ce qui coûte cher, c'est l'ignorance appliquée à un fichier entier : elle éteint la vérification pour de bon, et personne ne s'en aperçoit avant que le bug n'arrive.