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 conself.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__conselfimpostato sulla nuova istanza e gli altri argomenti passati così come sono.user.greet()chiama il metodo. Python passa automaticamenteusercomeself.
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.