Dic Python et typage avec typing.Dict : écrire un code plus robuste

Quand un dictionnaire Python grossit, les bugs liés à une clé absente ou à une valeur du mauvais type apparaissent souvent tard, en production. Le typage des dict Python avec le module typing permet de les détecter avant même l’exécution, directement dans l’éditeur ou via un outil comme mypy.

Cet article détaille la syntaxe à privilégier selon votre version de Python, les cas où TypedDict change la donne, et les pièges concrets que le typage ne résout pas seul.

dict natif ou typing.Dict : quelle syntaxe choisir selon la version de Python

Avant Python 3.9, annoter un dictionnaire passait par le module typing. On importait Dict puis on écrivait Dict[str, int] pour décrire un dictionnaire dont les clés sont des chaînes et les valeurs des entiers.

Depuis Python 3.9, les génériques intégrés rendent cet import inutile. On écrit directement dict[str, int], sans majuscule, sans import. La syntaxe est identique pour list, tuple et set.

Pourquoi préférer la forme native ? Moins d’imports, une cohérence visuelle avec le reste du code, et c’est la direction prise par la communauté. Utilisez dict minuscule dès que votre projet cible Python 3.9 ou plus récent.

Si vous maintenez du code compatible avec des versions antérieures (3.7 ou 3.8), typing.Dict reste fonctionnel. Mais dans tout nouveau projet, il n’y a plus de raison de l’utiliser.

Développeuse étudiant le typage Python avec le module typing et des annotations Dict dans un bureau à domicile chaleureux

Typage des valeurs dans un dict Python : au-delà de str et int

Annoter dict[str, str] couvre le cas simple. Les situations réelles sont rarement aussi nettes.

Union et Optional pour les valeurs mixtes

Un dictionnaire de configuration peut contenir des chaînes, des entiers et parfois None. On utilise alors dict[str, str | int | None] (syntaxe Python 3.10+) ou Dict[str, Union[str, int, None]] pour les versions antérieures.

Cette annotation indique à l’outil de vérification que chaque valeur peut être de l’un de ces types. Toute opération incompatible (par exemple appeler .upper() sans vérifier que la valeur est bien une chaîne) sera signalée.

Dict imbriqués

Pour un dictionnaire dont les valeurs sont elles-mêmes des dictionnaires, l’annotation se compose naturellement : dict[str, dict[str, float]]. Lisible sur une ligne, mais dès qu’on dépasse deux niveaux, la lisibilité chute. C’est précisément le cas d’usage de TypedDict, abordé dans la section suivante.

TypedDict : typer chaque clé individuellement

Avec dict[str, Any], on sait que les clés sont des chaînes, mais on ne sait rien de leur nom ni du type précis de chaque valeur. TypedDict résout ce problème en décrivant la forme exacte du dictionnaire.

Voici un exemple concret :

class UserProfile(TypedDict):
name: str
age: int
email: str

Toute fonction recevant un UserProfile sait que la clé age est un entier. Un appel comme profile["age"].upper() sera immédiatement signalé par mypy.

Champs obligatoires et optionnels avec Required et NotRequired

Depuis Python 3.11, on peut marquer chaque champ individuellement grâce à Required et NotRequired, sans toucher au paramètre global total.

class Config(TypedDict):
host: Required[str]
port: Required[int]
debug: NotRequired[bool]

Ce niveau de granularité évite de créer deux classes séparées (une stricte, une permissive) pour le même type de données. Chaque clé porte sa propre contrainte d’obligation.

Quand TypedDict ne suffit pas

TypedDict ne valide rien à l’exécution. C’est une annotation, pas un garde-fou runtime. Si les données proviennent d’une API externe ou d’un fichier JSON, le dictionnaire reçu peut très bien ne pas correspondre au schéma déclaré.

Pour la validation à l’exécution, des bibliothèques comme Pydantic prennent le relais. TypedDict et Pydantic ne s’opposent pas : le premier décrit la structure pour l’analyse statique, le second vérifie les données réellement reçues.

Deux développeurs en session de code review collaborant sur des annotations de type Dict en Python dans un bureau de startup

Mypy et dict Python : erreurs détectées avant l’exécution

Annoter ses dictionnaires sans jamais lancer un vérificateur statique revient à installer une alarme sans la brancher. Mypy est l’outil le plus répandu pour exploiter ces annotations.

Voici ce que mypy détecte sur un dict typé :

  • L’accès à une clé absente du TypedDict (par exemple profile["adresse"] alors que seule email existe).
  • L’affectation d’une valeur incompatible (profile["age"] = "trente" au lieu d’un entier).
  • L’oubli d’une clé obligatoire lors de la construction du dictionnaire.
  • L’appel d’une méthode incompatible avec le type de la valeur (comme .split() sur un entier).

Mypy signale ces erreurs sans exécuter le programme, ce qui raccourcit la boucle de correction. Dans un projet de plusieurs milliers de lignes, cette détection précoce évite des heures de débogage.

Pièges fréquents avec le typage des dict en Python

Annoter ses dictionnaires ne rend pas le code magiquement fiable. Quelques erreurs reviennent régulièrement.

  • Confondre annotation et validation : le typage statique n’empêche pas un dictionnaire mal formé d’arriver à l’exécution. Sans validation runtime, une API peut renvoyer une clé manquante que mypy n’aura jamais vue.
  • Abuser de dict[str, Any] : cette annotation désactive de fait toute vérification sur les valeurs. Chaque Any est un trou dans le filet de sécurité. Mieux vaut un TypedDict même partiel.
  • Oublier la compatibilité descendante : écrire dict[str, int] dans un projet qui tourne encore sur Python 3.8 provoque une erreur de syntaxe à l’exécution, pas un simple avertissement mypy.

Le typage des dictionnaires Python gagne en précision à chaque version du langage. Les annotations dict[str, int] couvrent le cas courant, TypedDict structure les données complexes, et Required/NotRequired affinent le contrôle champ par champ. L’étape la plus rentable reste d’intégrer mypy dans sa chaîne de développement : sans vérification automatique, même les meilleures annotations restent décoratives.

Les immanquables