Dataclass

0️⃣ Introduzione
Le @dataclass
in Python automatizzano la creazione di alcuni metodi speciali per le classi, che altrimenti dovremmo definire manualmente. Questo include metodi come __init__
, __repr__
, __eq__
, semplificando notevolmente la gestione di classi che fungono principalmente da contenitori di dati.
Per utilizzarle, basta importare il decoratore @dataclass
dal modulo dataclasses
:
from dataclasses import dataclass
e applicarlo alla classe @dataclass
.
1️⃣ Esempio pratico
Vediamo la differenza tra una classe normale e una @dataclass
con un esempio concreto.
- Senza usare la
@dataclass
:class Point: x: int y: int def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f"Point(x={self.x}, y={self.y})" def __eq__(self, other): return self.x == other.x and self.y == other.y p1 = Point(1, 2) p2 = Point(2, 1) print(p1, p2) print(p1 == p2)
⚙️Output:
Point(x=1, y=2) Point(x=2, y=1)
FalseCome si può vedere, dobbiamo scrivere manualmente sia il costruttore (
__init__
), sia i metodi__repr__
e__eq__
. Ora, vediamo come si comporta la stessa classe con@dataclass
- Usando la
@dataclass
:from dataclasses import dataclass @dataclass class Point: x: int y: int p1 = Point(1, 2) p2 = Point(2, 1) print(p1, p2) print(p1 == p2)
⚙️Output:
Point(x=1, y=2) Point(x=2, y=1)
FalseCome si può notare, l'output è lo stesso, ma non è stato necessario definire a mano i metodi
__init__
,__repr__
o__eq__
. La@dataclass
li ha generati automaticamente per noi.
2️⃣ Dettagli automatici
Quando usiamo @dataclass
, Python genera automaticamente i seguenti metodi:
__init__
: per inizializzare gli attributi
__repr__
: per rappresentare l'oggetto in modo leggibile
__eq__
: per confrontare due oggetti
3️⃣ Esempio Avanzato
Ecco un altro esempio, in cui definiamo una classe per un inventario:
from typing import ClassVar
@dataclass
class InventoryItem:
'''Class for keeping track of an item in inventory'''
name: str
unit_price: float
quantity_on_hand: int = 0
class_var: ClassVar[int] = 100
In questo modo il costruttore che verrà generato ignorerà la variabile “class_var”.
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
Possiamo vedere come la @dataclass
ha semplificato molto la gestione degli attributi della classe.
4️⃣ Come ignorare variabili nella dataclass
In alcuni casi, potremmo voler evitare che alcune variabili vengano considerate nel costruttore generato automaticamente. Per esempio, potremmo avere variabili che non devono essere inizializzate dall'esterno. Possiamo ottenere questo comportamento usando ClassVar
.
from typing import ClassVar
from dataclasses import dataclass
@dataclass
class InventoryItem:
'''Classe per tenere traccia degli articoli di inventario'''
name: str
unit_price: float
quantity_on_hand: int = 0
class_var: ClassVar[int] = 100
In questo caso, la variabile class_var
non sarà inclusa nel costruttore.
5️⃣ Ereditarietà
Classi miste
Quando ereditiamo da una classe che non è una @dataclass
, è necessario gestire manualmente alcune parti del processo di inizializzazione. Ad esempio:
from dataclasses import dataclass
class Rectangle:
def __init__(self, height, width):
self.height = height
self.width = width
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
È necessario definire il metodo __post_init__
manualmente.
Chiaramente, come precedentemente, per la classe dataclass verranno automaticamente generati i metodi __init__
, __repr__
e __eq__
Ereditarietà con sole dataclass
Quando sia la classe base che quella derivata sono @dataclass
, Python gestisce tutto automaticamente. Vediamo un esempio:
from dataclasses import dataclass
@dataclass
class Rectangle:
width: int
lenght: int
@dataclass
class ColorRectangle(Rectangle):
color: str
rect = ColorRectangle(10, 10, "green")
In questo caso, non è necessario definire manualmente né il costruttore né il metodo __post_init__
, poiché tutto viene generato automaticamente da Python.