Quando una funzione appartiene chiaramente a una classe, ma non ha bisogno né di self né di cls, @staticmethod offre una soluzione semplice e leggibile. In questo articolo mostro come funziona una static method in Python, quando conviene usarla, come distinguerla da un metodo di istanza o di classe e quali errori evitare, anche negli esempi legati all’accesso ai database.
La regola essenziale per usare bene i metodi statici
- Nessun self o cls: il metodo non riceve automaticamente né l’istanza né la classe.
- Funzione organizzata nella classe: è utile quando il comportamento appartiene concettualmente a quella classe.
- Chiamata flessibile: puoi usarlo sia dalla classe sia da un’istanza.
- Non sostituisce sempre una funzione di modulo: se il legame con la classe è debole, una funzione normale può essere più chiara.
- Nei database non basta per la sicurezza: la costruzione delle query deve comunque usare parametri e API appropriate.

Che cosa fa davvero @staticmethod
Un metodo statico è una funzione definita dentro una classe che Python non lega automaticamente all’oggetto chiamante. In pratica, quando lo invoco, non viene aggiunto alcun primo argomento nascosto: né l’istanza né la classe entrano nella chiamata.
class Convertitore:
@staticmethod
def metri_in_chilometri(metri):
return metri / 1000
print(Convertitore.metri_in_chilometri(3500))
# 3.5
Il decoratore @staticmethod comunica una scelta precisa. La funzione è collocata dentro Convertitore perché riguarda quel concetto, ma il suo risultato dipende soltanto dai parametri ricevuti. Io lo considero soprattutto uno strumento di organizzazione del codice, non un modo per rendere una funzione più veloce o più potente.
È possibile chiamarla anche da un’istanza:
convertitore = Convertitore()
print(convertitore.metri_in_chilometri(2000))
# 2.0
La chiamata funziona, ma l’istanza non viene usata. Per questo preferisco la forma Convertitore.metri_in_chilometri(...), che rende subito evidente l’assenza di stato interno.
Come definire un metodo statico utile
Un buon esempio è una funzione di normalizzazione. Immaginiamo una classe che gestisce codici cliente provenienti da file, API o database. La normalizzazione non deve leggere attributi dell’oggetto e può quindi vivere come metodo statico.
class CodiceCliente:
@staticmethod
def normalizza(valore):
if not isinstance(valore, str):
raise TypeError("Il codice deve essere una stringa")
return valore.strip().upper().replace(" ", "")
codice = CodiceCliente.normalizza(" ab 12 ")
print(codice)
# AB12
Qui la funzione è deterministica: allo stesso input corrisponde lo stesso output, senza dipendere da configurazioni esterne o attributi dell’istanza. Questo la rende facile da testare e adatta a essere riutilizzata in più punti dell’applicazione.
Un caso pratico con i dati del database
Nei progetti reali capita spesso di ricevere righe dal database e dover controllare che abbiano una forma valida prima di trasformarle in oggetti. Il metodo statico può occuparsi della validazione, mentre la connessione resta responsabilità del repository.
class UtenteRepository:
@staticmethod
def riga_valida(riga):
if not isinstance(riga, tuple):
return False
if len(riga) != 3:
return False
id_utente, email, stato = riga
return (
isinstance(id_utente, int)
and isinstance(email, str)
and "@" in email
and stato in {"attivo", "sospeso"}
)
def salva_riga(self, connessione, riga):
if not self.riga_valida(riga):
raise ValueError("Riga utente non valida")
connessione.execute(
"INSERT INTO utenti (id, email, stato) VALUES (?, ?, ?)",
riga
)
La funzione riga_valida() non sa nulla della connessione e non esegue query. Questa separazione è importante perché evita di mescolare validazione, accesso ai dati e gestione della transazione. Naturalmente, un metodo statico non rende automaticamente sicuro il codice SQL: per i valori dinamici servono sempre query parametrizzate.
La differenza tra metodo statico, di istanza e di classe
La scelta diventa più semplice se guardo al tipo di informazione di cui il metodo ha bisogno. Un metodo di istanza riceve normalmente self, un metodo di classe riceve cls, mentre uno statico non riceve alcun riferimento implicito.
| Tipo | Argomento automatico | Quando usarlo | Esempio tipico |
|---|---|---|---|
| Metodo di istanza | self |
Quando legge o modifica i dati dell’oggetto | Modificare l’indirizzo di un cliente |
| Metodo di classe | cls |
Quando lavora con la classe o crea nuove istanze | Creare un oggetto partendo da una riga SQL |
| Metodo statico | Nessuno | Quando serve una funzione collegata concettualmente alla classe | Validare o normalizzare un valore |
Per esempio, una factory che costruisce un utente da una riga del database è spesso più adatta a @classmethod, perché deve poter creare anche istanze di eventuali sottoclassi.
class Utente:
def __init__(self, id_utente, email):
self.id_utente = id_utente
self.email = email
@classmethod
def da_riga(cls, riga):
return cls(riga[0], riga[1])
@staticmethod
def email_valida(email):
return isinstance(email, str) and "@" in email
In questo esempio email_valida() controlla soltanto il valore, mentre da_riga() usa cls per costruire l’oggetto corretto. Confondere i due ruoli porta spesso a classi meno flessibili, soprattutto quando il progetto cresce o introduce sottoclassi.
Quando conviene usarlo e quando evitarlo
Io uso un metodo statico quando la funzione ha un legame semantico evidente con la classe, ma non ha bisogno del suo stato. Un validatore per gli ordini dentro Ordine è comprensibile; una funzione matematica generica inserita in una classe solo per raccogliere codice lo è molto meno.
- Usalo per validazioni, conversioni e parsing legati a un dominio preciso.
- Usalo per piccoli helper chiamati da altri metodi della stessa classe.
- Usalo quando il nome della classe rende la funzione più facile da trovare e capire.
- Preferisci una funzione a livello di modulo se il comportamento è generale e indipendente dalla classe.
Una funzione di modulo è spesso la scelta migliore quando potrebbe essere riutilizzata da molte parti dell’applicazione. Inserire tutto nelle classi solo per mantenere un’organizzazione apparentemente ordinata crea il rischio di trasformare le classi in semplici contenitori di funzioni.
C’è anche un limite importante nei test. Un metodo statico è facile da testare se è puro, ma se legge file, variabili globali, orologio di sistema o servizi esterni, il decoratore non risolve il problema della dipendenza. In quel caso servono iniezione delle dipendenze, interfacce più chiare o una separazione architetturale migliore.
Gli errori più comuni con @staticmethod
Aggiungere self senza motivo
class Calcolatore:
@staticmethod
def somma(self, a, b):
return a + b
Questo metodo non riceve automaticamente self, ma la firma lo richiede comunque. La chiamata Calcolatore.somma(2, 3) produrrà un errore perché mancherà un argomento. Se la funzione non usa l’istanza, rimuovere self è la correzione giusta.
Usare self o cls dentro il corpo
class Profilo:
nome_predefinito = "Ospite"
@staticmethod
def descrizione():
return self.nome_predefinito
Il codice fallisce perché self non esiste in quel contesto. Se serve un attributo dell’istanza, bisogna usare un metodo normale; se serve un attributo della classe, la soluzione più naturale è spesso @classmethod.
Confondere ordine e sicurezza delle query
Un metodo statico può validare un identificatore o preparare una struttura dati, ma non deve essere presentato come una protezione completa contro l’injection. Tabelle, colonne e valori hanno regole diverse, quindi per le parti dinamiche è necessario seguire le possibilità offerte dal driver del database e mantenere separati parametri e istruzioni SQL.
Leggi anche: Ciclo for in Python - guida a range, enumerate, zip e SQLite
Usarlo soltanto per imitare altri linguaggi
Chi arriva da Java o C# tende a cercare subito l’equivalente dei metodi statici. In Python, però, una funzione di modulo è spesso più idiomatica. La domanda che mi pongo è semplice: la classe aiuta davvero a capire dove appartiene questa funzione? Se la risposta è no, non forzo la scelta.
Il criterio pratico per scegliere senza complicare il codice
Prima di aggiungere @staticmethod, verifico tre cose. La funzione deve avere bisogno solo dei parametri espliciti, deve essere collegata in modo chiaro alla classe e deve diventare più facile da trovare grazie a quella collocazione.
Se usa dati dell’oggetto, scelgo un metodo di istanza. Se crea o gestisce oggetti usando la classe, considero @classmethod. Se è una trasformazione indipendente ma specifica del dominio della classe, il metodo statico è spesso il compromesso più pulito.
La sintassi è minima, ma la decisione architetturale conta. Un metodo statico ben scelto rende il codice più leggibile e i test più semplici; usato senza un vero legame con la classe, aggiunge soltanto un livello di indirezione che il progetto non aveva bisogno di avere.