Kodokon kodokon.com

Type hints et dataclasses : typer sérieusement du Python moderne

Comprenez ce que les annotations font réellement à l'exécution et exploitez les dataclasses avec frozen, slots et default_factory.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Les annotations de type sont des métadonnées, pas des contraintes : l'interpréteur les range dans __annotations__ et ne les vérifie jamais à l'exécution. La vérification est le travail d'un analyseur statique comme mypy ou pyright, avant l'exécution. Conséquence directe : un programme mal typé s'exécute sans broncher - et c'est voulu. Le typage graduel permet d'annoter progressivement un code existant sans rien casser.

PYTHON
from typing import get_type_hints

def add(a: int, b: int) -> int:
    return a + b

print(add("py", "thon"))
print(add.__annotations__)
print(get_type_hints(add))
Les annotations ne bloquent rien : l'appel « incorrect » réussit.

Depuis Python 3.9, les types natifs sont directement génériques : écrivez list[int] ou dict[str, float] sans importer typing.List. Les unions s'écrivent int | None depuis 3.10. Pour lier le type d'entrée au type de sortie, utilisez un TypeVar - Python 3.12 offre même la syntaxe compacte def first[T](items: list[T]) -> T. Point souvent ignoré : from __future__ import annotations rend toutes les annotations paresseuses (stockées comme chaînes), ce qui autorise les références à des classes définies plus bas et allège le coût à l'import.

PYTHON
from collections.abc import Iterable, Iterator
from typing import TypeVar

T = TypeVar("T")

def dedupe(items: Iterable[T]) -> Iterator[T]:
    seen: set[T] = set()
    for item in items:
        if item not in seen:
            seen.add(item)
            yield item

print(list(dedupe([3, 1, 3, 2, 1])))
Le TypeVar garantit que dedupe(list[int]) produit bien des int.
PYTHON
from dataclasses import dataclass, field

@dataclass(frozen=True, slots=True)
class Point:
    x: float
    y: float

@dataclass
class Basket:
    items: list[str] = field(default_factory=list)
    total: int = 0

    def __post_init__(self) -> None:
        self.total = len(self.items)

point = Point(1.0, 2.0)
print(point, hash(point))
print(Basket(["book", "pen"]))
Point est immuable, hashable et sans __dict__ par instance.

Trois paramètres font de @dataclass un outil de niveau production. frozen=True rend les instances immuables et, combiné à l'égalité générée, hashables - utilisables comme clés de dictionnaire. slots=True (Python 3.10+) supprime le __dict__ par instance : mémoire réduite, accès aux attributs plus rapides, et toute affectation d'attribut imprévu échoue. Enfin kw_only=True force les appels explicites par mot-clé. __post_init__ sert aux invariants et aux champs dérivés. Pour un simple agrégat immuable, NamedTuple reste pertinent ; dès qu'il y a comportement ou validation, préférez la dataclass.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Que se passe-t-il à l'exécution quand on appelle add('py', 'thon') alors que la fonction est annotée (a: int, b: int) -> int ?
    • Python lève immédiatement un TypeError
    • L'appel s'exécute normalement : les annotations ne sont jamais vérifiées à l'exécution
    • Python émet un RuntimeWarning puis exécute l'appel
    • Le module refuse de s'importer
  2. Pourquoi items: list[str] = [] est-il interdit comme défaut d'un champ de dataclass ?
    • Parce que list[str] n'est pas une annotation valide pour un champ
    • Parce que les dataclasses ne tolèrent que des champs sans valeur par défaut
    • Parce que ce défaut mutable serait partagé entre toutes les instances : la dataclass lève ValueError et impose field(default_factory=list)
    • Parce que les défauts doivent être définis dans __post_init__
  3. Quel est l'effet de frozen=True sur une dataclass ?
    • Les instances deviennent immuables et, avec l'égalité générée, hashables - utilisables comme clés de dictionnaire
    • La classe ne peut plus être héritée
    • Les champs deviennent des attributs de classe partagés
    • Les annotations sont vérifiées à chaque affectation