Définir les contrats de données
Objectif : Modéliser une dépense et sa catégorie à l’aide d’annotations explicites, puis utiliser ces types dans une agrégation simple.
Quand une dépense passe d’un module à un autre, une description, un montant et une catégorie mal interprétés peuvent fausser les totaux ou faire échouer le traitement. Vous allez poser un contrat de données explicite pour ces informations, afin que les outils de vérification puissent repérer certaines incohérences avant l’exécution.
Dans cette leçon, le contrat reste volontairement léger : une dépense est représentée par un tuple contenant sa description, son montant et sa catégorie. Vous utilisez Decimal pour les montants, afin d’éviter les approximations binaires courantes avec les nombres à virgule flottante. La catégorie peut être absente, par exemple lorsqu’une dépense n’a pas encore été classée.
Annoter les éléments du contrat
Une annotation indique le type attendu pour une variable, un paramètre ou une valeur renvoyée. Elle ne transforme pas, à elle seule, les données à l’exécution : elle documente le contrat et permet à un vérificateur statique de signaler des usages incompatibles. Pour les interfaces publiques, annotez systématiquement les paramètres et le type de retour.
Dans src/depenses/modeles.py, vous pouvez nommer les types utiles avec des alias. str représente la description et la catégorie ; Decimal représente le montant. L’annotation str | None indique qu’une catégorie est soit une chaîne, soit la valeur spéciale None. Cette syntaxe est disponible à partir de Python 3.10.
from decimal import Decimal
Description = str
Categorie = str | None
Depense = tuple[Description, Decimal, Categorie]
def creer_depense(
description: Description,
montant: Decimal,
categorie: Categorie,
) -> Depense:
"""Assemble les données d'une dépense dans l'ordre défini par le contrat."""
return (description, montant, categorie)
def total_des_depenses(depenses: list[Depense]) -> Decimal:
"""Additionne les montants d'une liste de dépenses."""
total = Decimal("0.00")
for _, montant, _ in depenses:
total += montant
return total
if __name__ == "__main__":
depenses = [
creer_depense("Déjeuner", Decimal("12.50"), "repas"),
creer_depense("Transport", Decimal("3.20"), None),
]
print(total_des_depenses(depenses))15.70Les alias rendent les annotations plus lisibles sans créer de nouveau comportement à l’exécution. Depense désigne ici un tuple ordonné : description en première position, montant en deuxième, catégorie en troisième. La fonction total_des_depenses reçoit une liste de ces dépenses et renvoie un Decimal. Les crochets de list[Depense] précisent le type des éléments de la collection.
Choisir des types cohérents
Un type doit correspondre aux valeurs réellement manipulées. Ici, le montant est un Decimal, pas une chaîne de caractères comme "12.50" ni un float. La conversion est faite à partir d’une chaîne dans l’exemple : Decimal("12.50") conserve la valeur décimale attendue. Ne construisez pas un montant en passant d’abord par un flottant, car son approximation peut déjà avoir été introduite.
La catégorie est optionnelle, mais elle n’est pas une chaîne vide par convention : son absence est représentée explicitement par None. Cela permet de distinguer une dépense non classée d’une dépense dont le libellé de catégorie serait vide. Pour utiliser ce module dans la suite du projet, les autres fonctions devront respecter le même contrat de montant et de catégorie.
Exécutez le module depuis la racine du projet avec python -m depenses.modeles, en ayant le paquet disponible dans votre environnement. Le résultat attendu est 15.70 : les deux montants ont été additionnés, que la catégorie soit renseignée ou non. Cette exécution confirme le comportement ; la vérification statique, étudiée plus tard, permettra de contrôler les annotations sans lancer le programme.
À retenir
- Annotez les paramètres et les retours des fonctions publiques pour rendre leur contrat visible.
- Utilisez des collections génériques, comme list[Depense], pour préciser les éléments attendus.
- Représentez les montants avec Decimal et construisez-les à partir de chaînes décimales.
- Utilisez str | None lorsque l’absence d’une catégorie est une valeur prévue du modèle.
Dans la leçon suivante, vous rendrez les échanges entre modules plus explicites avec des structures de données dédiées.