
Le décorateur @dataclass
Découvrez le décorateur @dataclass en Python (introduit en Python 3.7). Créez des classes de données concises et efficaces, avec des méthodes spéciales générées automatiquement (__init__, __repr__, __eq__, etc.).
Qu'est-ce qu'une dataclass ? Une classe pour stocker des données
Une dataclass (classe de données) est une classe Python conçue principalement pour stocker des données. Elle est similaire à une structure (`struct`) dans d'autres langages, ou à un `namedtuple`, mais avec plus de flexibilité.
Les dataclasses ont été introduites en Python 3.7 (PEP 557) pour simplifier la création de classes dont le rôle principal est de stocker des données.
Avant les dataclasses, il fallait écrire beaucoup de code répétitif pour créer une classe de données simple (définir `__init__`, `__repr__`, `__eq__`, etc.). Les dataclasses automatisent ce processus.
Le décorateur @dataclass : automatisation de la création de méthodes
Pour créer une dataclass, vous utilisez le décorateur `@dataclass` (du module `dataclasses`) avant la définition de la classe.
Le décorateur `@dataclass` examine les attributs définis dans la classe (avec des annotations de type) et génère automatiquement plusieurs méthodes spéciales, notamment :
- `__init__(self, ...)` : Le constructeur (pour initialiser les attributs).
- `__repr__(self)` : Une représentation textuelle de l'objet (pour l'affichage et le débogage).
- `__eq__(self, other)` : Pour comparer des objets (égalité).
- `__ne__(self, other)` : Pour comparer des objets (inégalité).
- `__lt__(self, other)`, `__le__(self, other)`, `__gt__(self, other)`, `__ge__(self, other)` : Pour comparer des objets (ordre), si l'option `order=True` est spécifiée.
- `__hash__(self)` : Pour rendre l'objet hachable (utilisable comme clé de dictionnaire), si l'option `unsafe_hash=False` (par défaut) et que les attributs sont immuables.
Vous pouvez personnaliser le comportement de `@dataclass` en utilisant des arguments (voir plus loin).
Syntaxe de base et exemple
Syntaxe de base :
from dataclasses import dataclass
@dataclass
class MaDataclass:
attribut1: type1
attribut2: type2 = valeur_par_defaut
# ...- `from dataclasses import dataclass` : Importe le décorateur `dataclass`.
- `@dataclass` : Applique le décorateur à la classe.
- `attribut1: type1` : Déclare un attribut et son type (les annotations de type sont obligatoires pour que `@dataclass` fonctionne).
- `attribut2: type2 = valeur_par_defaut` : Déclare un attribut avec une valeur par défaut.
Exemple :
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
z: float = 0.0 # Valeur par défaut
# Création d'objets
p1 = Point(1.0, 2.0, 3.0)
p2 = Point(4.0, 5.0)
print(p1) # Affiche Point(x=1.0, y=2.0, z=3.0) (__repr__ généré)
print(p1 == p2) # False (__eq__ généré)
Dans cet exemple, `@dataclass` a généré automatiquement `__init__`, `__repr__`, et `__eq__`. Vous n'avez pas besoin de les écrire vous-même.
Personnaliser le comportement avec les arguments de @dataclass
Vous pouvez personnaliser le comportement de `@dataclass` en lui passant des arguments. Voici les principaux arguments :
- `init=True` (par défaut) : Génère la méthode `__init__`.
- `repr=True` (par défaut) : Génère la méthode `__repr__`.
- `eq=True` (par défaut) : Génère les méthodes `__eq__` et `__ne__`.
- `order=False` (par défaut) : Si `True`, génère les méthodes de comparaison (`__lt__`, `__le__`, `__gt__`, `__ge__`).
- `unsafe_hash=False` (par défaut) : Si `True`, génère la méthode `__hash__`. Attention, cela peut être dangereux si la classe contient des attributs mutables.
- `frozen=False` (par défaut) : Si `True`, rend les instances de la classe immuables (après l'initialisation).
Exemple :
from dataclasses import dataclass
@dataclass(order=True, frozen=True) # Ordre et immuabilité
class Vecteur:
x: float
y: float
z: float
v1 = Vecteur(1, 2, 3)
v2 = Vecteur(4, 5, 6)
print(v1 < v2) # True (grâce à order=True, compare les attributs dans l'ordre)
# v1.x = 10 # Lèverait une FrozenInstanceError (grâce à frozen=True)Utiliser des champs (fields) pour plus de contrôle
Le module `dataclasses` fournit également la fonction `field()` qui permet de contrôler plus finement la façon dont les attributs sont traités par `@dataclass`.
Vous pouvez utiliser `field()` pour :
- Spécifier une valeur par défaut différente d'une simple valeur constante (par exemple, une liste vide, un dictionnaire vide).
- Exclure un attribut de la méthode `__init__`.
- Exclure un attribut de la méthode `__repr__`.
- Spécifier si un attribut doit être utilisé ou non pour la comparaison (`__eq__`, etc.).
- Définir un attribut comme étant calculé (et non initialisé dans `__init__`).
Exemple :
from dataclasses import dataclass, field
@dataclass
class Inventaire:
nom: str
prix_unitaire: float
quantite_en_stock: int = 0
elements: list = field(default_factory=list) # Valeur par défaut : liste vide
description: str = field(default="", repr=False) # Ne pas inclure dans le repr
#valeur_totale: float = field(init=False) # Attribut calculé (non initialisé dans __init__)
#Pour initialiser valeur_totale, on peut utiliser la méthode spéciale __post_init__
def __post_init__(self):
self.valeur_totale = self.prix_unitaire * self.quantite_en_stock
inventaire = Inventaire(nom = "Tasse à café", prix_unitaire= 4.99, quantite_en_stock= 10)
print(inventaire) #Inventaire(nom='Tasse à café', prix_unitaire=4.99, quantite_en_stock=10, elements=[], valeur_totale=49.90)
Dans cet exemple :
- `elements` utilise `field(default_factory=list)` pour s'assurer que chaque instance de `Inventaire` a sa propre liste vide, et non une liste partagée (ce qui serait le cas si on utilisait `elements: list = []`).
- `description` utilise `field(default="", repr=False)` pour avoir une valeur par défaut (chaîne vide) et pour ne pas être inclus dans la représentation textuelle de l'objet (générée par `__repr__`).
- `valeur_totale` utilise `field(init=False)` pour indiquer qu'il ne doit pas être initialisé dans le constructeur. On utilise ensuite la méthode spéciale `__post_init__` (appelée après `__init__`) pour calculer sa valeur.
Avantages des dataclasses
Les dataclasses offrent de nombreux avantages :
- Moins de code : Elles réduisent considérablement la quantité de code répétitif à écrire (plus besoin d'écrire `__init__`, `__repr__`, `__eq__`, etc. "à la main").
- Code plus clair : Elles rendent le code plus lisible et plus facile à comprendre.
- Meilleure maintenabilité : Il est plus facile de modifier et d'étendre les dataclasses que les classes traditionnelles équivalentes.
- Fonctionnalités supplémentaires : Elles offrent des options pour contrôler le comportement des classes (ordre, immuabilité, etc.).
- Intégration avec les annotations de type : Elles encouragent l'utilisation d'annotations de type, ce qui améliore la lisibilité et permet une meilleure vérification statique du code.
Les dataclasses sont un excellent ajout à Python et sont devenues un outil standard pour créer des classes de données simples et efficaces.