Sostieni AppuntiFacili con una piccola donazione su PayPal

Dona con PayPal
AppuntiFacili
Torna Indietro Segnala errore

Dataclass

✍️ Dennis Turco 🏷️ Informatica 📘 Python
Ultima modifica:
#python#programmazione#dataclass#class

1. 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
class NomeClasse:
    ...

2. 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)
    False

    Come si può vedere, dobbiamo scrivere manualmente sia il costruttore (__init__), sia i metodi __repr__ e __eq__.

  • 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)
    False

    L’output è lo stesso, ma non è stato necessario definire a mano i metodi __init__, __repr__ o __eq__. La @dataclass li ha generati automaticamente.

3. 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

4. Esempio Avanzato

Ecco un altro esempio, in cui definiamo una classe per un inventario:

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Classe per tenere traccia di un articolo di inventario"""
    name: str
    unit_price: float
    quantity_on_hand: int = 0
    class_var: ClassVar[int] = 100

INFO

Quando vogliamo indicare che un attributo appartiene alla classe (e non all’istanza), possiamo usare ClassVar. Queste variabili non vengono incluse nel costruttore generato automaticamente.

5. Ignorare o escludere variabili dal costruttore

In alcuni casi, potremmo voler escludere certi attributi dal costruttore automatico o dall’output del __repr__. Possiamo ottenere questo comportamento con field() dal modulo dataclasses.

from dataclasses import dataclass, field

@dataclass
class Libro:
    titolo: str
    autore: str
    prezzo: float
    _iva: float = field(default=0.1, repr=False)

INFO

field() consente di controllare come gli attributi vengono trattati dalla dataclass:

  • init=False: esclude l’attributo dal costruttore
  • repr=False: lo nasconde nella rappresentazione testuale
  • compare=False: lo esclude dal confronto ==

6. Ereditarietà

6.1 Classi miste

Quando ereditiamo da una classe che non è una @dataclass, è necessario gestire manualmente alcune parti dell’inizializzazione.

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)

INFO

Il metodo __post_init__ viene eseguito automaticamente dopo il costruttore generato da @dataclass. È utile quando servono operazioni di inizializzazione aggiuntive.

Esempio:

from dataclasses import dataclass

@dataclass
class Prodotto:
    nome: str
    prezzo: float
    iva: float = 0.22

    def __post_init__(self):
        self.prezzo_lordo = self.prezzo * (1 + self.iva)

6.2 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
    length: 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.

7. Opzioni aggiuntive di decorazione

OpzioneSignificato
frozen=TrueRende gli oggetti immutabili
order=TrueGenera anche i metodi di confronto <, <=, > e >=
kw_only=True (da Python 3.10)Rende obbligatorio passare i parametri per nome

Esempio:

from dataclasses import dataclass

@dataclass(frozen=True, order=True)
class Punto:
    x: int
    y: int

8. Esercizi

8.1 Esercizio

Creare una @dataclass chiamata “Libro” con:

  • titolo (stringa)
  • autore (stringa)
  • prezzo (float)
  • metodo __post_init__ che calcola prezzo_ivato con IVA al 10%
  • proprietà descrizione che restituisce una stringa tipo "Titolo di Autore - Prezzo: XX€"

9. Quiz a risposta multipla

1) Qual è lo scopo principale del decoratore @dataclass in Python?

2) Quale modulo è necessario importare per usare le dataclass?

3) Quale dei seguenti metodi viene generato automaticamente da @dataclass?

4) Cosa fa il parametro repr=False nel campo definito con field()?

5) Come si esclude un attributo dal costruttore automatico di una dataclass?

6) Qual è lo scopo del metodo __post_init__ in una dataclass?

7) Quale opzione del decoratore dataclass rende un oggetto immutabile?

8) Quando è utile usare ClassVar all'interno di una dataclass?

9) Quale parametro del decoratore dataclass genera anche i metodi di ordinamento (<, <=, >, >=)?

10) Quale vantaggio offre l'uso di @dataclass rispetto a una classe tradizionale?

Prenota una lezione