Contactez-nous

Génération automatique de méthodes (__init__, __repr__, etc.)

Explorez en détail la génération automatique des méthodes spéciales (__init__, __repr__, __eq__, __lt__, etc.) par le décorateur @dataclass en Python. Comprenez comment personnaliser ce comportement.

Le rôle du décorateur @dataclass : automatiser la création de code

Le décorateur `@dataclass` (du module `dataclasses`) est l'élément clé qui permet la génération automatique de méthodes spéciales dans les dataclasses.

Lorsque vous décorez une classe avec `@dataclass`, Python analyse les attributs définis dans la classe (avec des annotations de type) et génère automatiquement les méthodes suivantes (par défaut) :

  • `__init__`
  • `__repr__`
  • `__eq__`
  • `__ne__`

D'autres méthodes peuvent être générées en fonction des arguments passés à `@dataclass` (par exemple, `order=True` pour générer les méthodes de comparaison, `frozen=True` pour l'immuabilité).

Cela évite d'avoir à écrire manuellement ces méthodes, ce qui réduit la quantité de code répétitif et améliore la lisibilité.

__init__ : initialisation automatique des attributs

Le constructeur `__init__` généré par `@dataclass` prend des arguments correspondant aux attributs définis dans la classe (dans l'ordre de leur définition) et les utilise pour initialiser les attributs de l'objet.

Les attributs avec des valeurs par défaut auront des valeurs par défaut correspondantes dans le constructeur.

Exemple :

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0

# Le __init__ généré est équivalent à :
# def __init__(self, x: float, y: float, z: float = 0.0):
#     self.x = x
#     self.y = y
#     self.z = z

p = Point(1.0, 2.0)  # z prend la valeur par défaut 0.0
print(p)  # Affiche Point(x=1.0, y=2.0, z=0.0)

Vous pouvez toujours définir votre propre méthode `__init__` si vous avez besoin d'une logique d'initialisation plus complexe. Dans ce cas, `@dataclass` ne générera pas de méthode `__init__`.

__repr__ : représentation textuelle automatique

La méthode `__repr__` générée par `@dataclass` crée une représentation textuelle de l'objet, qui ressemble à l'appel du constructeur qui a créé l'objet.

Cette représentation est utile pour le débogage et l'inspection des objets.

Exemple (avec la classe `Point` précédente) :

p = Point(1.0, 2.0, 3.0)
print(repr(p))  # Affiche Point(x=1.0, y=2.0, z=3.0)

# La représentation peut (généralement) être utilisée pour recréer l'objet :
# p2 = eval(repr(p))
# print(p2 == p)  # True

Vous pouvez désactiver la génération de `__repr__` en passant `repr=False` à `@dataclass`.

__eq__ et __ne__ : comparaison d'égalité automatique

Les méthodes `__eq__` et `__ne__` générées par `@dataclass` permettent de comparer des objets de la dataclass en utilisant les opérateurs `==` et `!=`.

Par défaut, la comparaison se fait attribut par attribut, dans l'ordre de définition des attributs.

Exemple (avec la classe `Point` précédente) :

p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
p3 = Point(4.0, 5.0)

print(p1 == p2)  # True (tous les attributs sont égaux)
print(p1 == p3)  # False
print(p1 != p3) # True

Vous pouvez désactiver la génération de `__eq__` et `__ne__` en passant `eq=False` à `@dataclass`.

Autres méthodes générées (order, frozen, unsafe_hash)

En fonction des arguments passés à `@dataclass`, d'autres méthodes spéciales peuvent être générées :

  • `order=True` : Génère `__lt__`, `__le__`, `__gt__`, et `__ge__`. Cela permet de comparer des objets de la dataclass avec les opérateurs `<`, `<=`, `>`, et `>=`. La comparaison se fait attribut par attribut, dans l'ordre de définition des attributs.
  • `frozen=True` : Rend les instances de la dataclass immuables. Toute tentative de modification d'un attribut après la création de l'objet lèvera une `FrozenInstanceError`. De plus un `__hash__` est généré, rendant la classe hashable (et donc utilisable comme clé de dictionnaire ou élément d'un set).
  • `unsafe_hash=True` : Force la génération d'une méthode `__hash__`, même si la classe n'est pas immuable. C'est "unsafe" (dangereux) car si les attributs de l'objet sont modifiés après que l'objet a été utilisé comme clé de dictionnaire (ou élément d'un set), le comportement du dictionnaire (ou du set) est indéfini. A n'utiliser que si vous êtes certain que la classe est immuable *de facto*.

Exemple :

from dataclasses import dataclass

@dataclass(order=True, frozen=True)
class Vecteur:
    x: float
    y: float
    z: float

v1 = Vecteur(1, 2, 3)
v2 = Vecteur(4, 5, 6)
v3 = Vecteur(1, 2, 3)

print(v1 < v2)  # True (comparaison lexicographique des attributs)
# v1.x = 10    # Lèverait une FrozenInstanceError

print(hash(v1)) # Possible car frozen=True. v1 est hashable.
print(v1 == v3) # True

Personnalisation avec field()

La fonction `field()` du module `dataclasses` permet de contrôler plus finement la façon dont les attributs sont traités par `@dataclass`.

Voici quelques arguments utiles de `field()`:

  • `default` : Spécifie une valeur par défaut pour l'attribut.
  • `default_factory` : Spécifie une fonction (ou un callable) qui sera appelée sans argument pour fournir une valeur par défaut. Utile pour les valeurs par défaut mutables (comme les listes ou les dictionnaires).
  • `init=True` (par défaut) : Indique si l'attribut doit être inclus dans le constructeur généré par `@dataclass`.
  • `repr=True` (par défaut) : Indique si l'attribut doit être inclus dans la représentation textuelle générée par `__repr__`.
  • `compare=True` (par défaut) : Indique si l'attribut doit être utilisé pour les comparaisons (`==`, `!=`, `<`, etc.).
  • `hash=None` (par défaut) : Contrôle si l'attribut doit être utilisé pour le hachage (`__hash__`).
  • `metadata=None` (par défaut) : Un dictionnaire (ou un objet mappable) pour stocker des métadonnées arbitraires sur l'attribut. `@dataclass` n'utilise pas ces métadonnées, mais elles peuvent être utiles pour des outils tiers.

L'utilisation de `field()` permet de personnaliser la génération des méthodes spéciales de la dataclass.