Tout sur dataclass en Python
Le décorateur @dataclass
est apparu avec Python 3.7 pour répondre à un besoin clair : structurer des données simplement, sans écrire de code répétitif.
C'est une solution native et élégante pour créer des classes lisibles, concises, typées et puissantes, tout en réduisant significativement le code à écrire (on y vient juste après). C’est un outil plébiscité aussi bien par les développeurs débutants que les experts.
Lorsqu’on l’applique à une classe, Python génère automatiquement plusieurs méthodes spéciales comme :
__init__()
pour l’initialisation des attributs ;__repr__()
pour une représentation lisible ;__eq__()
pour la comparaison d’objets.
et même d’autres méthodes si on le souhaite (__lt__
, __le__
, etc).
Cela permet de se concentrer sur les données, tout en ayant un objet pleinement fonctionnel.
Contrairement aux
namedtuple
, lesdataclass
sont plus souples, acceptent le typage, la mutabilité ou l’immuabilité, et peuvent contenir des méthodes personnalisées.
Syntaxe de base et exemple
Pour utiliser une dataclass
, il suffit d'ajouter @dataclass
(qui est un décorateur) au-dessus d'une classe pour activer ses fonctionnalités.
Voici un petit exemple de dataclass
pour illustrer toute sa puissance :
from dataclasses import dataclass
@dataclass
class Produit:
nom: str
prix: float
article = Produit("Clavier", 49.99)
print(article.nom) # Clavier
print(article.prix) # 49.99
print(article) # Produit(nom='Clavier', prix=49.99)
En seulement 4 lignes, nous avons une classe typée avec un constructeur automatique. Magique, non ? 😋
Pas besoin de
__init__
,__str__
, ni de définir les types manuellement dans des méthodes :@dataclass
le fait pour nous.
Les paramètres clés du décorateur @dataclass
Le décorateur @dataclass
peut être configuré à l’aide de paramètres optionnels, selon le comportement désiré :
Paramètre | Description |
init=True | Génère automatiquement la méthode __init__() |
repr=True | Génère __repr()__ pour faire une représentation |
eq=True | Génère __eq__() pour faire des comparaisons d'égalité |
order=False | Permet de comparer l'infériorité et la supériorité |
frozen=False | Rend l'objet immuable (si True) |
Les valeurs par défaut et les champs optionnels
Avec dataclass
, on peut facilement spécifier des valeurs par défaut pour certains champs, comme dans une fonction classique :
from dataclasses import dataclass
@dataclass
class Article:
nom: str
prix: float = 0.0
Dans cet exemple, si l'on crée un Article("Câble USB")
, le prix sera automatiquement 0.0
.
Le module
dataclasses
propose également la fonctionfield()
pour gérer des cas avancés :PYTHONfrom dataclasses import dataclass, field @dataclass class Commande: articles: list = field(default_factory=list)
Ici,
default_factory
est très utile pour éviter les pièges liés aux valeurs mutables partagées comme les listes ou les dictionnaires.
Rendre une dataclass immuable (frozen=True)
Lorsque nous ajoutons frozen=True
, l’objet devient immuable : ses attributs ne peuvent plus être modifiés après la création.
from dataclasses import dataclass
@dataclass(frozen=True)
class Client:
nom: str
age: int
c = Client("Jean", 30)
# c.age = 31 ❌ Provoque une erreur : cannot assign to field
D'ailleurs, les frozen dataclass
sont hashables automatiquement si leurs champs le sont. Vous pouvez ainsi les utiliser comme clés dans un dictionnaire ou dans un set
.
Comparaison et tri avec order=True
Par défaut, une dataclass
ne peut pas être triée ni comparée avec <
, >
, <=
, >=
.
Pour activer ces opérations, nous pouvons préciser le paramètre order=True
.
from dataclasses import dataclass
@dataclass(order=True)
class Produit:
prix: float
nom: str
L’ordre de tri se base sur l’ordre des champs dans la déclaration : ici, les instances seront triées selon le prix
, puis selon le nom
si les prix sont égaux.
La méthode spéciale __post_init__()
La méthode __post_init__()
est appelée après l’exécution de __init__()
générée automatiquement par @dataclass
. Elle permet de réaliser des traitements ou des vérifications personnalisées sur les attributs.
from dataclasses import dataclass
@dataclass
class Personne:
nom: str
age: int
def __post_init__(self):
if self.age < 0:
raise ValueError("L'âge ne peut pas être négatif.")
Dans cet exemple même si __init__()
est automatique on ajoute une logique métier spécifique sans avoir à la réécrire.
On utilise souvent
__post_init__()
pour valider, arrondir ou transformer des données d'entrée. 😉
Typage fort et type hints
L’un des grands avantages des dataclass
est leur intégration native avec les annotations de type.
Chaque champ est typé avec une annotation standard, ce qui offre plusieurs bénéfices :
- Une documentation claire ;
- Un meilleur support avec notre IDE (auto-complétion, vérification) ;
- L'intégration avec des outils de vérification statique (MyPy, Pyright).
Voici comme faire :
@dataclass
class Compte:
identifiant: int
solde: float
actif: bool
Grâce à ces types les outils peuvent détecter si une mauvaise valeur est passée à l’instanciation :
c = Compte("abc", 50.0, True) # 🚫 Erreur détectable avec MyPy
Le typage est recommandé, mais pas obligatoire. Toutefois, sans types, certaines fonctionnalités de
dataclass
ne fonctionneront pas correctement (comme la génération de__init__()
).
dataclass vs classe classique : quelles sont les différences ?
Voyons maintenant une comparaison concrète entre une classe classique et une dataclass
.
L’objectif est de mettre en évidence la réduction de code et le gain de lisibilité.
Classe classique :
class Produit:
def __init__(self, nom, prix):
self.nom = nom
self.prix = prix
def __repr__(self):
return f"Produit(nom={self.nom!r}, prix={self.prix!r})"
def __eq__(self, other):
return isinstance(other, Produit) and self.nom == other.nom and self.prix == other.prix
Version dataclass
:
from dataclasses import dataclass
@dataclass
class Produit:
nom: str
prix: float
Résultat : même fonctionnalité, 5 fois moins de code, plus clair, plus propre.
dataclass vs namedtuple
Avant dataclass
, on utilisait souvent namedtuple
pour créer des objets légers avec nommage de champs. Comparons-les :
Critère | dataclass | namedtuple |
Nécessite l'import | ✅ Oui (dataclasses ) | ✅ Oui (collections ) |
Mutable ? | ✅ Oui | ❌ Non (immuable) |
Typé ? | ✅ Oui | ❌ Non |
Méthodes personnalisées ? | ✅ Oui | ❌ Pas vraiment |
Héritage | ✅ Oui | ❌ Complexe |
Triable ? | ✅ Oui | ✅ Oui (par défaut) |
En résumé : dataclass
est plus moderne et plus flexible, tandis que namedtuple
reste utile si vous voulez de l’immuabilité simple sans surcharge.
Les options avancées avec field()
Comme nous l'avons vu, le module dataclasses
fournit l’utilitaire field()
pour personnaliser finement le comportement de chaque attribut.
Voyons ensemble ses paramètres :
Option | Description |
default= | Valeur par défaut |
default_factory= | Génère dynamiquement une valeur par défaut (top pour les listes et les dictionnaires) |
init=False |
Ne pas inclure dans |
repr=False | Exclut du __repr__() |
compare=False | Exclut de __eq__() et __lt__() |
Prenons cet exemple :
from dataclasses import dataclass, field
@dataclass
class Compteur:
nom: str
historique: list = field(default_factory=list, repr=False, compare=False)
Dans cet exemple :
historique
est invisible dans__repr__()
;- Il ne sert pas à la comparaison entre deux objets ;
- Il reçoit une nouvelle liste à chaque instance, sans partage de référence (évite les pièges).
Comme nous pouvons l'apercevoir, field()
est un outil très puissant pour affiner les règles de nos objets dataclass
, surtout dans les contextes orientés API, sérialisation ou logique métier.
Utiliser asdict() et astuple()
Les dataclass
peuvent être converties facilement en dictionnaire ou en tuple grâce aux fonctions utilitaires asdict()
et astuple()
du module dataclasses
.
from dataclasses import dataclass, asdict, astuple
@dataclass
class Produit:
nom: str
prix: float
p = Produit("Stylo", 2.50)
print(asdict(p)) # {'nom': 'Stylo', 'prix': 2.5}
print(astuple(p)) # ('Stylo', 2.5)
Ces conversions sont utiles :
Pour la sérialisation vers JSON ;
Pour l’affichage de debug ;
Ou pour envoyer les données via une API.
asdict()
effectue une conversion récursive : si un champ contient une autre dataclass, elle sera également convertie.
Héritage et dataclasses imbriquées
Héritage des dataclasses
Comme les classes Python "classiques", les dataclass
supportent l’héritage. Cela permet de factoriser des attributs communs ou d’enrichir des classes spécialisées.
from dataclasses import dataclass
@dataclass
class Personne:
nom: str
age: int
@dataclass
class Employe(Personne):
poste: str
Ici, Employe
hérite des champs de Personne
et ajoute un champ poste
.
Les dataclasses imbriquées
Les dataclasses imbriquées sont idéales pour représenter des objets complexes ou hiérarchisés.
@dataclass
class Adresse:
ville: str
code_postal: str
@dataclass
class Client:
nom: str
adresse: Adresse
c = Client("Chloé", Adresse("Paris", "75001"))
print(c.adresse.ville) # Paris
Performances et limitations des dataclasses
Performances
Les dataclass
sont aussi rapides que les classes classiques pour la majorité des usages, mais il convient de ne pas oublier que les méthodes automatiques ajoutent une légère surcharge à l'instanciation (c'est vraiment dérisoire).
Aussi, les objets frozen=True
sont un peu plus lent car hashables évidemment.
Limitations
Les dataclasses ne remplacent par un ORM ou un modèle métier complet, elles sont aussi peu adaptées au objets très dynamiques ou pour l'héritage multiple.
Enfin : elles sont uniquement compatibles à partir de Python 3.7.
Cas pratiques d'utilisation
Les dataclass
sont utilisées dans de nombreux contextes concrets. Faisons un petit tour avec quelques exemples.
Avec des données simples
@dataclass
class Utilisateur:
nom: str
email: str
actif: bool = True
Idéal pour manipuler des objets utilisateurs dans une API.
Avec des configurations
@dataclass
class Config:
debug: bool
chemin: str
version: float
Avec des données métiers
@dataclass
class Commande:
produit: str
quantite: int
prix_unitaire: float
def total(self) -> float:
return self.quantite * self.prix_unitaire
Questions fréquentes sur les dataclasses
Faisons un petit point sur les questions les plus fréquentes avec les dataclasses en Python !
Est-ce que je peux ajouter des méthodes dans une
dataclass
?
Oui ! Les dataclass
sont des classes Python classiques, donc vous pouvez ajouter des méthodes comme dans toute autre classe.
Puis-je modifier les attributs d'une
dataclass
?
Oui, sauf si vous avez défini frozen=True
. Dans ce cas, les instances deviennent immuables.
Est-ce compatible avec les versions < 3.7 ?
Non. Le module dataclasses
est natif à partir de Python 3.7. Pour les versions antérieures, une backport existe : pip install dataclasses
.
Où apprendre à maîtriser Python ?