Qu'est-ce que les dataclasses en python ?

Introduites avec la PEP 557 et Python 3.7, les dataclasses simplifient la création de classes. En effet, elles sont très utilisées pour les classes dédiées au stockage de données, avec très peu de code.
Par exemple, les dataclasses automatisent la génération de méthodes __init__, __repr__ et __eq__.

Nous allons voir les différents avantages qu'apportent les dataclasses :

  • Simplicité de déclaration

  • Lisibilité du code

  • Réduction du code

  • Flexibilité et personnalisation

Qu'est-ce qu'une dataclass ?

Avec une classe standard, il est nécessaire d'implémenter le constructeur ainsi que d'autres méthodes pour définir les attributs et les afficher.
Les dataclasses permettent de se concentrer sur la définition des attributs tout en laissant Python gérer beaucoup de choses pour nous. Ainsi le code est beaucoup plus concis et lisible.

Prenons un exemple, la définition d'une classe Voiture :

class Voiture:
    def __init__(self, marque, modele, annee, couleur, kilometrage):
        self.marque = marque
        self.modele = modele
        self.annee = annee
        self.couleur = couleur
        self.kilometrage = kilometrage

    def __repr__(self):
        return f"Voiture(marque='{self.marque}', modele='{self.modele}', annee={self.annee}, couleur='{self.couleur}', kilometrage={self.kilometrage})"

    def __eq__(self, other):
        if not isinstance(other, Voiture):
            return False
        return (self.marque == other.marque and 
                self.modele == other.modele and 
                self.annee == other.annee and 
                self.couleur == other.couleur and 
                self.kilometrage == other.kilometrage)

Pour faire la même chose avec les dataclasses :

from dataclasses import dataclass

@dataclass
class Voiture:
    marque: str
    modele: str
    annee: int
    couleur: str
    kilometrage: int

La version avec dataclass est donc beaucoup plus concise.

Syntaxe de base d'une dataclass

Pour déclarer une dataclass, il suffit d'ajouter le décorateur @dataclass à la déclaration de la classe. De plus, chaque attribut doit être annoté avec un type.
Voici un exemple simple :

from dataclasses import dataclass

@dataclass
class Personne:
    nom: str
    age: int

Votre classe est prête à être instanciée. Toujours avec ce même exemple, on remarque que __repr__ est automatiquement générée :

from dataclasses import dataclass

@dataclass
class Personne:
    nom: str
    age: int


personne = Personne(nom="Patrick", age=30)
print(repr(personne)) # Affiche Personne(nom='Patrick', age=30)

Paramètres du décorateur @dataclass

Les dataclasses sont personnalisables via plusieurs paramètres qui permettent d'ajuster le comportement des dataclasses.
Voici quelques paramètres avec des exemples :

  • init génère automatiquement la méthode __init__
    @dataclass(init=False), en définissant init à False, il faut implémenter la méthode __init__ manuellement.

  • repr génère la méthode __repr__

  • eq génère la méthode __eq__ qui permet de comparer les instances entre elles (via leurs attributs)

  • order par défaut défini à False, permet d'ordonner les instances en ajoutant les méthodes __lt__, __le__, __gt__ et __ge__

from dataclasses import dataclass

@dataclass(order=True)
class Student:
    grade: float
    name: str
    age: int

# Création de quelques étudiants
student1 = Student(15.5, "Patrick", 20)
student2 = Student(14.0, "Robert", 21)
student3 = Student(15.5, "Ely", 19)
student4 = Student(9.0, "Damien", 22)

# Maintenant on peut comparer et trier les étudiants
# Par défaut, la comparaison se fait sur tous les champs dans l'ordre de déclaration
print(student1 > student2)  # True (car 15.5 > 14.0)
print(student1 == student3)  # False car les noms sont différents

# On peut aussi trier une liste d'étudiants
students = [student2, student3, student1, student4]
sorted_students = sorted(students)
for student in sorted_students:
    print(f"{student.name}: {student.grade}")
"""
Damien: 9.0
Robert: 14.0
Ely: 15.5
Patrick: 15.5
"""
  • frozen permet de rendre l'instance immuable après sa création
from dataclasses import dataclass


@dataclass(frozen=True)
class FrozenClass:
    name: str
    age: int

gab = FrozenClass(name="Gab", age=30)
gab.age = 35 # Lève dataclasses.FrozenInstanceError

Nous avons vu les principaux paramètres qui permettent une personnalisation fine de nos classes, en ne générant que les méthodes nécessaires.

Les champs d'une dataclass

Précédemment, nous avons vu que chaque attribut doit être annoté avec son type. De plus, il est possible de personnaliser une dataclass via ses attributs.

Valeur par défaut

Avec les dataclasses, il est possible d'assigner une valeur par défaut :

from dataclasses import dataclass

@dataclass
class Personne:
    nom: str
    age: int = 30

p = Personne("Dupont")
print(p.nom)  # Affiche "Dupont"
print(p.age)  # Affiche 30

La fonction field

Pour affiner davantage le comportement d'une dataclass, on peut utiliser la fonction field. Cette dernière accepte plusieurs paramètres.

from dataclasses import dataclass, field

@dataclass
class Vehicule:
    modele: str
    marque: str
    places: int = field(default=5, repr=False)

v = Vehicule("Model 3", "Tesla")
print(repr(v)) # Vehicule(modele='Model 3', marque='Tesla')

Dans cet exemple, le champ places a une valeur par défaut et n'apparaît pas avec __repr__.

default_factory est un autre paramètre essentiel pour travailler avec des objets mutables. Il faut lui passer un objet callable.

Python lèvera une erreur si vous définissez une liste en valeur par défaut :

from dataclasses import dataclass
from typing import List

@dataclass
class MaCLasse:
    liste: List[int] = [1, 2, 3]
# Raise ValueError: mutable default <class 'list'> for field liste is not allowed: use default_factory

Une sécurité importante qui permet d'empêcher que toutes les instances partagent la même liste. Pour ne pas avoir d'erreur, il faut penser à utiliser le paramètre default_factory :

from dataclasses import dataclass, field
from typing import List

@dataclass
class MaCLasse:
    liste: List[int] = field(default_factory=list)

Personnaliser l'initialisation avec __post_init__

La méthode __post_init__ est automatiquement appelée après la méthode __init__. Elle permet d'effectuer des opérations supplémentaires comme :

  • la validation de données

  • le formatage de données

  • le calcul de champs dérivés

Voici quelques exemples :

from dataclasses import dataclass


@dataclass
class Email:
    address: str
    priority: str = "normal"

    def __post_init__(self):
        # Validation simple du format email
        if '@' not in self.address or '.' not in self.address:
            raise ValueError("Format d'email invalide")

# Exemples d'utilisation
email1 = Email("[email protected]", "HIGH")
print(email1)  # Email(address='[email protected]', priority='high')

email2 = Email("invalid")  # Lève ValueError: Format d'email invalide
from dataclasses import dataclass, field


@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)  # champ calculé, non inclus dans l'init

    def __post_init__(self):
        self.area = self.width * self.height  # calcul automatique après initialisation


# Exemple d'utilisation
rectangle = Rectangle(width=5, height=3)
print(rectangle.area)  # Affiche: 15

Bravo, tu es prêt à passer à la suite

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.