Menu

Classi in Python: __init__, self, metodi, ereditarietà e dataclass

Un'introduzione per principianti alle classi in Python: come definirle, creare istanze, il metodo __init__, l'ereditarietà e quando usare invece le dataclass.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Una classe raggruppa i dati e quello che ci fai

Una classe ti permette di definire un tipo di cosa (un utente, un conto bancario, una configurazione, una richiesta) e tutto quello che quella cosa deve saper fare. La cosa è un'istanza della classe. Le azioni si chiamano metodi.

Ecco la classe più semplice possibile:

Vediamola pezzo per pezzo:

  • class User: inizia la definizione di una classe. Per convenzione i nomi delle classi usano il PascalCase.
  • __init__ è l'inizializzatore. Viene eseguito automaticamente quando crei una nuova istanza. Imposta gli attributi dell'istanza con self.whatever = ....
  • greet è un metodo. self è il modo in cui un metodo si riferisce all'istanza su cui è stato chiamato.
  • User("Rosa", "rosa@example.com") crea un'istanza. Python chiama __init__ con self impostato sulla nuova istanza e gli altri argomenti passati così come sono.
  • user.greet() chiama il metodo. Python passa automaticamente user come self.

self è solo il primo argomento

self non è una parola chiave. È il nome dato al primo parametro dei metodi di istanza, cioè l'istanza su cui il metodo sta girando. Potresti chiamarlo in qualsiasi modo; tutti usano self perché lo fanno tutti.

Ogni metodo di istanza riceve self come primo parametro. È così che un metodo sa da quale istanza leggere o quale modificare.

Aggiungere comportamento

I metodi possono leggere e modificare lo stato:

__repr__ è un altro metodo speciale: un aggancio che dice "come mostro questo oggetto quando viene stampato?". Definirne uno fatto bene ripaga ampiamente durante il debug.

Attributi di classe e attributi di istanza

Gli attributi definiti nel corpo della classe (fuori da __init__) sono condivisi tra tutte le istanze. Gli attributi assegnati a self sono propri di ogni istanza:

Usa gli attributi di classe per valori davvero condivisi: costanti, contatori, configurazione. Metti i dati specifici dell'istanza in __init__.

Ereditarietà

Una classe può ereditare da un'altra, ottenendone gratis metodi e attributi e aggiungendo o ridefinendo quello che serve:

super() ti dà accesso alla classe genitore. Si usa spesso dentro __init__ per inizializzare gli attributi del genitore prima di aggiungere i tuoi:

L'ereditarietà è utile ma è facile abusarne. Chi inizia con Python ricorre spesso all'ereditarietà quando la composizione (tenere un oggetto come attributo) sarebbe più chiara. Parti con una struttura di classi piatta e introduci l'ereditarietà solo quando c'è una vera relazione "è un".

La pagina sull'ereditarietà in Python va oltre: estendere un metodo del genitore con super(), isinstance e issubclass, classi base astratte, ereditarietà multipla e ordine di risoluzione dei metodi.

Dataclass: una scelta migliore per i record

Quando ti serve soprattutto una classe che contenga qualche attributo con un comportamento di uguaglianza e di rappresentazione, @dataclass ti risparmia un sacco di codice ripetitivo:

Confrontalo con lo scrivere a mano __init__, __repr__ e __eq__. Le dataclass sono la prima scelta giusta quando altrimenti scriveresti una class Thing con solo un __init__ che imposta attributi.

Property: attributi calcolati

A volte vuoi un "attributo" che in realtà viene calcolato. @property lo fa sembrare un normale accesso a un attributo:

Usa le property quando scriveresti obj.get_something(): sostituirlo con una property rende l'API più naturale. Non usarle per nascondere operazioni costose; chi le usa si aspetta che l'accesso a un attributo sia veloce.

Privato per convenzione

Python non ha un vero concetto di privato. Per convenzione, gli attributi con un underscore iniziale sono pensati come privati: chi legge il codice non dovrebbe toccarli dall'esterno della classe:

class Cache:
    def __init__(self):
        self._store = {}

    def put(self, key, value):
        self._store[key] = value

    def get(self, key, default=None):
        return self._store.get(key, default)

I nomi con doppio underscore (__name) attivano il name mangling, che aggiunge il nome della classe all'attributo. È uno strumento per evitare collisioni nell'ereditarietà, non una misura di sicurezza.

Quando non usare una classe

Se hai solo un insieme di dati collegati, una dataclass, un semplice dict o una named tuple vanno benissimo. Se hai solo una funzione, non serve una classe per contenerla. Ricorri alle classi quando hai sia stato sia comportamento legati insieme, soprattutto se avrai molte istanze dello stesso tipo.

Un esempio finale

Un piccolo buffer di log simile a un file:

Nota __len__, un altro metodo magico. Definirlo fa funzionare len(logger), perché len(x) in realtà è una chiamata a x.__len__(). Python ha decine di questi agganci; li imparerai man mano che ti servono.

Domande frequenti

Cos'è una classe in Python?

Una classe è un modello per creare oggetti che raggruppano dati (attributi) e comportamento (metodi). class User: definisce una classe User; User(...) crea un'istanza. Le classi sono lo strumento principale di Python per modellare cose che hanno sia uno stato sia un comportamento.

Cosa significa self in Python?

self è il primo parametro di ogni metodo di istanza. Quando chiami user.greet(), Python passa l'istanza user come self. Dentro il metodo, self.name accede all'attributo name di quell'utente. Il nome self è una convenzione, non una parola chiave, ma lo usano tutti i programmatori Python.

Python è orientato agli oggetti?

Sì: in Python tutto è un oggetto, compresi numeri, stringhe e funzioni. Puoi programmare a oggetti quando ha senso, ma Python non ti obbliga. Molti programmi Python reali usano poche classi e preferiscono, dove possibile, funzioni semplici e strutture dati.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA