Cours de Python / Décrire les types et vérifier les appels avec mypy
Les indications de type décrivent les entrées et résultats attendus pour les lecteurs, les éditeurs et les outils d’analyse statique. L’interpréteur Python ordinaire ne les applique pas automatiquement : une annotation ne remplace ni la conversion d’une saisie ni la validation à l’exécution.
Lire une annotation comme un contrat de type
Dans amount: int, int indique le type attendu ; il ne transforme pas la chaîne "8000" en nombre. La notation -> str annonce un résultat de type chaîne. Une fonction peut déclarer ce contrat et avoir malgré tout un comportement incorrect : les outils de vérification et les essais servent à repérer ces écarts.
dict[str, str] décrit ici un dictionnaire dont les clés et les valeurs sont des chaînes ; list[int] désignerait une liste d’entiers. Le type union str | None indique qu’un résultat peut être une chaîne ou être absent. Il ne signifie pas que l’argument correspondant peut être omis : les valeurs par défaut sont une autre partie de la signature.
Ce cours utilise Python 3.12 ou ultérieur pour rester cohérent avec les exercices précédents. Le code n’inspecte pas dynamiquement les annotations ; si vous développez cette inspection, consultez les règles d’évaluation de la version choisie. La documentation Python du typage distingue les annotations de leur utilisation par les outils.
Comparer le contrat et le contrôle réellement exécuté
Enregistrez le programme complet en UTF-8 dans hints.py, dans un dossier d’exercice. Son exécution ne demande aucun paquet externe. Depuis ce dossier, lancez python hints.py avec l’environnement virtuel du projet sélectionné. Utilisez python3 hints.py si python3 est votre commande hors environnement virtuel sous macOS ou Ubuntu.
def format_amount(amount: int) -> str:
if type(amount) is not int or amount < 0:
raise ValueError("invalid amount")
return f"{amount:,} KRW"
def find_name(names: dict[str, str], key: str) -> str | None:
return names.get(key)
print(format_amount(8000))
print(find_name({"e1": "lunch"}, "missing"))
try:
format_amount("8000")
except ValueError:
print("rejected")
Résultat attendu :
8,000 KRW
None
rejected
La première ligne provient du formatage d’un entier valide. La virgule regroupe les milliers selon le format explicite du programme ; KRW reste un libellé de devise. La seconde ligne est None, car la clé missing n’existe pas. La troisième, rejected, apparaît parce que le contrôle écrit dans format_amount lève ValueError pour l’argument chaîne, puis que le gestionnaire capture cette exception.
Ce rejet vient du if, pas de l’annotation int. Le contrôle exige aussi une valeur positive ou nulle et exclut les booléens par le test du type exact. Un outil statique peut comprendre qu’un appel transmet str au lieu de int ; il ne déduit pas nécessairement votre règle métier interdisant les montants négatifs. Le cours sur les exceptions et la validation explique le comportement exécuté.
Installer et comparer un contrôle statique
Pour ajouter une analyse avec mypy, activez l’environnement virtuel du projet et exécutez les deux commandes suivantes dans le dossier de hints.py :
python -m pip install mypy
python -m mypy hints.py
Utilisez le même interpréteur pour pip, mypy et le programme. Si vous avez choisi python3, adaptez les deux commandes. L’installation ajoute l’outil à l’environnement ; son exécution analyse le fichier sans lancer le programme, comme le précise le guide de démarrage officiel de mypy.
Le fichier contient volontairement format_amount("8000"). Mypy doit signaler un argument str incompatible avec le paramètre int, même si cet appel est dans un try. La présence d’un gestionnaire ValueError ne rend pas le contrat de type correct. La formulation exacte du diagnostic dépend de la version de l’outil.
Conservez hints.py et créez une copie nommée hints_valid.py. Dans cette copie, remplacez uniquement cet appel par format_amount(8000), puis lancez python -m mypy hints_valid.py. Le contrôle doit réussir. Si vous exécutez aussi la copie, elle affiche 8,000 KRW et None ; rejected disparaît car l’appel est devenu valide. Relancez le fichier original pour retrouver ses trois lignes.
| Vérification | Ce qu’elle contrôle ici |
|---|---|
| Analyse statique | Compatibilité des types déclarés, notamment str passé à un paramètre int. |
| Validation à l’exécution | Valeur réellement reçue, type exact et montant non négatif. |
N’élargissez pas tous les types à Any pour faire disparaître les diagnostics : vous retirez alors des informations utiles à l’analyse. Un contrôle statique réussi ne garantit pas non plus l’existence d’un fichier, la disponibilité du réseau ni la validité métier de données externes.
Traiter un résultat absent et les limites d’entrée
La fonction find_name renvoie None lorsqu’elle ne trouve pas la clé. Si vous voulez appeler une méthode de chaîne sur son résultat, conservez-le dans une variable name, testez name is not None, puis utilisez la méthode dans cette branche. Dans le cas contraire, traitez explicitement l’absence. Le guide mypy des types optionnels décrit ce rétrécissement du type après un contrôle.
Un test de vérité tel que if name écarterait aussi une chaîne vide. Choisissez-le seulement si votre règle assimile effectivement une valeur vide à une absence ; le test is not None distingue les deux. Vérifiez cette différence avec un dictionnaire contenant une clé associée à "" et avec une clé manquante.
Essayez dans une copie un appel format_amount(-1) : son type est bien int, mais la fonction le refuse à l’exécution. Comparez aussi True, refusé par ce contrôle exact, même si bool appartient à la hiérarchie de int. Ces exemples montrent que contrat statique et politique d’entrée ne sont pas identiques.
Enfin, si une API renvoie le montant sous forme de texte, décidez si cette forme doit être convertie ou rejetée. Écrire une annotation int ne prend pas cette décision. Vérifiez chaque chemin de retour et chaque frontière externe pour que le contrat annoncé corresponde aux valeurs effectivement fournies.




