Introduzione
Quando si inizia a lavorare con le classi in Python, spesso l’attenzione si concentra sugli attributi e sul costruttore __init__. Però, uno degli aspetti più utili e concreti nella progettazione orientata agli oggetti è capire quando usare un metodo di istanza e quando usare un metodo di classe.
Questo sotto-argomento è molto pratico perché aiuta a scrivere codice più pulito, più leggibile e più facile da mantenere. In particolare, i metodi di classe sono perfetti per creare costruttori alternativi, gestire configurazioni condivise o costruire oggetti da formati diversi, come stringhe o dizionari.
Vedremo un esempio realistico: una classe Task per rappresentare un’attività di un piccolo sistema di gestione del lavoro. La classe avrà:
- un costruttore standard per creare un oggetto completo;
- un metodo di classe per creare un task a partire da una stringa;
- un metodo di istanza per aggiornare lo stato del task;
- un altro metodo di classe per creare task di default.
Questo approccio è molto comune in progetti reali, perché i dati non arrivano sempre già nel formato perfetto per il costruttore.
Codice completo
from datetime import datetime
class Task:
# Attributo di classe: condiviso da tutte le istanze
default_priority = "media"
def __init__(self, titolo, scadenza, priorita=None, completato=False):
self.titolo = titolo
self.scadenza = scadenza
self.priorita = priorita if priorita is not None else Task.default_priority
self.completato = completato
def segna_completato(self):
"""Metodo di istanza: modifica lo stato dell´oggetto corrente."""
self.completato = True
def riepilogo(self):
"""Metodo di istanza: restituisce una stringa descrittiva del task."""
stato = "completato" if self.completato else "da fare"
return f"[{stato}] {self.titolo} - scadenza: {self.scadenza} - priorità: {self.priorita}"
@classmethod
def da_stringa(cls, testo):
"""
Metodo di classe: crea un Task partendo da una stringa nel formato:
´Titolo | YYYY-MM-DD | priorita´
Esempio:
´Scrivere report | 2026-07-10 | alta´
"""
parti = [p.strip() for p in testo.split("|")]
if len(parti) != 3:
raise ValueError("Formato non valido. Usa: ´Titolo | YYYY-MM-DD | priorita´")
titolo = parti[0]
data_testo = parti[1]
priorita = parti[2]
# Verifica e conversione della data
try:
scadenza = datetime.strptime(data_testo, "%Y-%m-%d").date()
except ValueError:
raise ValueError("La data deve avere il formato YYYY-MM-DD")
return cls(titolo=titolo, scadenza=scadenza, priorita=priorita)
@classmethod
def task_di_default(cls):
"""Metodo di classe: crea un task predefinito."""
return cls(
titolo="Task senza titolo",
scadenza=datetime.today().date(),
priorita=cls.default_priority
)
# --- Esempi di utilizzo ---
# Creazione classica con il costruttore
task1 = Task("Studiare Python", datetime(2026, 7, 1).date(), "alta")
print(task1.riepilogo())
# Creazione tramite metodo di classe
task2 = Task.da_stringa("Scrivere report | 2026-07-10 | media")
print(task2.riepilogo())
# Creazione di un task di default
task3 = Task.task_di_default()
print(task3.riepilogo())
# Uso di un metodo di istanza
task2.segna_completato()
print(task2.riepilogo()) Spiegazione
Vediamo con calma cosa succede nel codice.
1. Il costruttore __init__
Il metodo __init__ è un metodo di istanza speciale che viene chiamato quando crei un nuovo oggetto. Nel nostro esempio riceve i dati fondamentali del task:
- titolo: nome dell’attività;
- scadenza: data entro cui completarla;
- priorita: livello di importanza;
- completato: stato iniziale dell’attività.
Se la priorità non viene specificata, usiamo default_priority, che è un attributo di classe. Questo significa che il valore è condiviso tra tutte le istanze, a meno che un oggetto non lo sovrascriva.
2. Il metodo di istanza segna_completato
Questo metodo lavora su un oggetto specifico. Quando chiami task2.segna_completato(), stai modificando solo quell’istanza. È il comportamento tipico dei metodi di istanza: agiscono sui dati dell’oggetto corrente tramite self.
In pratica, un metodo di istanza è la scelta giusta quando l’operazione dipende dallo stato di un singolo oggetto.
3. Il metodo di classe da_stringa
Il decoratore @classmethod trasforma il metodo in un metodo di classe. Il primo parametro non è self, ma cls, che rappresenta la classe stessa.
Questo è molto utile quando vuoi creare oggetti in modi alternativi. Nel nostro esempio, il metodo prende una stringa come:
"Scrivere report | 2026-07-10 | media"
e la converte in un oggetto Task. Il vantaggio è chiaro: il codice che usa la classe non deve occuparsi della logica di parsing ogni volta.
Un dettaglio importante: dentro il metodo usiamo cls(...) invece di scrivere direttamente Task(...). In questo modo il metodo funziona bene anche se la classe viene ereditata in futuro.
4. Il metodo di classe task_di_default
Questo metodo crea un task già pronto con valori predefiniti. È un altro esempio di costruttore alternativo. In progetti reali, questo pattern è utile quando:
- vuoi offrire un modo semplice per creare oggetti validi;
- hai configurazioni standard ripetute spesso;
- vuoi nascondere dettagli ripetitivi al chiamante.
5. Differenza pratica tra metodi di istanza e di classe
- Metodo di istanza: usa self, lavora su un oggetto già creato, modifica o legge il suo stato.
- Metodo di classe: usa cls, lavora sulla classe, spesso serve per creare oggetti o accedere a dati condivisi.
Questa distinzione è fondamentale per progettare classi ben organizzate. Se un metodo non ha bisogno dello stato di un oggetto specifico, probabilmente non dovrebbe essere un metodo di istanza.
Best practice
- Usa metodi di classe per costruttori alternativi: sono ideali quando devi creare oggetti da formati diversi, come stringhe, file o dizionari.
- Preferisci cls a nome_classe: chiamare la classe tramite cls rende il codice più flessibile e adatto all’ereditarietà.
- Non abusare dei metodi di classe: se un’operazione non crea oggetti e non lavora sulla classe, forse è meglio una funzione esterna o un metodo di istanza.
- Valida sempre i dati in ingresso: nel nostro esempio controlliamo il formato della stringa e della data, così evitiamo errori silenziosi.
- Usa nomi chiari: nomi come da_stringa o task_di_default comunicano subito l’intento del metodo.
- Separa parsing e logica di dominio: il metodo di classe può occuparsi della conversione dei dati, mentre il costruttore resta semplice e coerente.
Riepilogo
I metodi di istanza e i metodi di classe sono strumenti complementari. I primi servono per operare su un oggetto specifico, i secondi per creare oggetti in modo alternativo o gestire informazioni condivise a livello di classe.
Nel tutorial abbiamo visto una classe Task che mostra un uso realistico di entrambi:
- __init__ per la creazione standard;
- segna_completato per modificare lo stato dell’istanza;
- da_stringa per costruire oggetti da testo;
- task_di_default per fornire una scorciatoia utile.
Capire bene questa distinzione ti aiuta a scrivere classi più eleganti, più testabili e più vicine ai casi d’uso reali.
Approfondisci con risorse ufficiali
- Documentazione ufficiale Python - Classi: https://docs.python.org/3/tutorial/classes.html
- Documentazione ufficiale Python - Data model: https://docs.python.org/3/reference/datamodel.html
- Documentazione ufficiale Python - classmethod(): https://docs.python.org/3/library/functions.html#classmethod
- PEP 8 - Style Guide for Python Code: https://peps.python.org/pep-0008/
