Aller au contenu principal

Indications de type pratiques en Python

Les indications de type documentent les contrats attendus et facilitent analyse statique, édition et refactorisation. Python n'impose pas les annotations de fonctions ou variables à l'exécution. Validez séparément les entrées non fiables.

from collections.abc import Iterable

def average(values: Iterable[float]) -> float:
numbers = list(values)
if not numbers:
raise ValueError("values must not be empty")
return sum(numbers) / len(numbers)

Annotez d'abord les frontières publiques et les modèles internes importants. Les variables locales que le vérificateur peut inférer ont rarement besoin d'annotations.

Cibler une version de Python

La syntaxe de typage évolue. Déclarez la version minimale du projet, configurez le vérificateur en conséquence et utilisez typing_extensions lorsqu'une fonctionnalité récente doit être rétroportée.

À partir de Python 3.10, préférez les génériques intégrés et les unions | :

def lookup(names: list[str], fallback: str | None = None) -> str: ...

L'instruction type et la syntaxe de paramètres génériques entre crochets exigent Python 3.12 ou ultérieur. Utilisez les formes historiques TypeAlias et TypeVar si la cible l'exige.

Modéliser le sens, pas les détails de stockage

  • Acceptez des capacités abstraites comme Iterable, Sequence ou Mapping si aucun conteneur concret n'est requis.
  • Renvoyez un type concret si l'appelant dépend de son comportement.
  • Employez Literal ou une énumération pour un petit ensemble fermé de valeurs significatives.
  • Utilisez TypedDict pour les enregistrements en forme de dictionnaire, et des classes de données ou ordinaires pour les valeurs avec comportement et invariants.
  • Employez Protocol pour les interfaces structurelles ; l'héritage n'est pas requis.
  • Limitez Any aux frontières non typées et raffinez-le vite. Any désactive les contrôles utiles au lieu de signifier « inconnu mais sûr ».
from typing import Protocol

class SupportsClose(Protocol):
def close(self) -> None: ...

def finish(resource: SupportsClose) -> None:
resource.close()

Raffinement et absence

Traitez les unions à l'aide de preuves réelles à l'exécution :

def length(value: str | bytes | None) -> int:
if value is None:
return 0
if isinstance(value, bytes):
return len(value)
return len(value)

Optional[T] signifie T | None ; il ne signifie pas que le paramètre a une valeur par défaut. N'utilisez pas cast à la place d'un contrôle : l'appel ne change que la vue du vérificateur statique.

Frontière d'exécution

Les annotations peuvent être représentées ou évaluées différemment selon les versions prises en charge. Le code qui les introspecte doit utiliser des API documentées comme typing.get_type_hints et tenir compte des imports, références anticipées et effets possibles de l'évaluation.

Un flux utile exécute un vérificateur configuré en CI, traite les suppressions comme des exceptions étroites et documentées, et teste séparément le comportement réel. L'accord du vérificateur ne prouve pas la correction ; il indique qu'une classe d'incohérences de contrats n'a pas été trouvée.

Sources

Explorer les liens

Cette note n’a pas encore de liens vers d’autres notes.

Sur les mêmes sujets (46)

Ouvrir le réseau