Metodi di classe e metodi statici in Python: organizzare meglio il comportamento degli oggetti

by theArchitect
SHARE
Metodi di classe e metodi statici in Python: organizzare meglio il comportamento degli oggetti
© Guida-HTML5.it

Introduzione

Quando si inizia a studiare la programmazione a oggetti in Python, è naturale concentrarsi soprattutto sugli attributi e sui metodi di istanza. Un metodo di istanza lavora su un oggetto specifico e riceve automaticamente il parametro self. Tuttavia, non tutte le operazioni hanno bisogno di conoscere una particolare istanza.

In questi casi possiamo usare due strumenti molto utili: i metodi di classe, definiti con @classmethod, e i metodi statici, definiti con @staticmethod. Entrambi appartengono alla classe, ma hanno scopi diversi:

  • un metodo di classe riceve la classe attraverso il parametro cls;
  • un metodo statico non riceve automaticamente né l’istanza né la classe;
  • un metodo di classe è utile per creare oggetti in modi alternativi;
  • un metodo statico è adatto a funzioni correlate alla classe, ma indipendenti dai suoi dati.

Un caso pratico è la gestione degli ordini di un negozio. Un ordine può essere creato normalmente passando direttamente i dati, oppure costruito leggendo una riga di testo proveniente da un file o da un sistema esterno. Il metodo di classe può occuparsi della seconda modalità, mentre un metodo statico può validare un codice prodotto senza accedere allo stato dell’ordine.

Codice completo

class Ordine:
    aliquota_iva = 0.22

    def __init__(self, cliente, prodotto, prezzo, quantita):
        self.cliente = cliente
        self.prodotto = prodotto
        self.prezzo = prezzo
        self.quantita = quantita

    def totale_senza_iva(self):
        """Calcola il totale imponibile dell´ordine."""
        return self.prezzo * self.quantita

    def totale_con_iva(self):
        """Calcola il totale comprensivo di IVA."""
        imponibile = self.totale_senza_iva()
        return imponibile * (1 + self.aliquota_iva)

    @classmethod
    def da_riga_csv(cls, riga):
        """
        Crea un Ordine a partire da una stringa nel formato:
        cliente,prodotto,prezzo,quantita
        """
        parti = [elemento.strip() for elemento in riga.split(",")]

        if len(parti) != 4:
            raise ValueError("La riga deve contenere esattamente quattro valori")

        cliente, prodotto, prezzo, quantita = parti

        return cls(
            cliente=cliente,
            prodotto=prodotto,
            prezzo=float(prezzo),
            quantita=int(quantita)
        )

    @staticmethod
    def codice_prodotto_valido(codice):
        """
        Verifica che il codice abbia il formato PROD-1234.
        """
        if not isinstance(codice, str):
            return False

        parti = codice.split("-")

        if len(parti) != 2:
            return False

        prefisso, numero = parti
        return prefisso == "PROD" and numero.isdigit() and len(numero) == 4


ordine_1 = Ordine("Giulia", "Tastiera", 45.90, 2)

ordine_2 = Ordine.da_riga_csv(
    "Marco,Mouse wireless,25.50,3"
)

print(ordine_1.totale_con_iva())
print(ordine_2.totale_senza_iva())

print(Ordine.codice_prodotto_valido("PROD-2048"))
print(Ordine.codice_prodotto_valido("MOUSE-12"))

Spiegazione

Il metodo di classe e il parametro cls

Il decoratore @classmethod modifica il modo in cui Python invoca un metodo. Il primo parametro non è self, ma cls, che rappresenta la classe stessa.

Nel metodo da_riga_csv, l’espressione cls(...) crea una nuova istanza. È preferibile usare cls invece di scrivere direttamente Ordine(...), perché questo rende il metodo più flessibile. Se in futuro una sottoclasse erediterà Ordine, potrà riutilizzare il metodo e ottenere un oggetto della sottoclasse.

Un metodo di classe è spesso chiamato costruttore alternativo. Il costruttore standard __init__ riceve i valori già separati, mentre da_riga_csv sa interpretare un formato esterno. In questo modo la logica di conversione rimane dentro la classe e non viene duplicata nel programma principale.

Il metodo statico

Il metodo codice_prodotto_valido è decorato con @staticmethod. Non riceve automaticamente self o cls, perché per eseguire il controllo gli basta il valore passato come argomento.

La funzione è comunque inserita nella classe perché riguarda direttamente il dominio degli ordini e dei prodotti. Chi legge il codice capisce subito dove trovare questa regola. Il metodo potrebbe esistere anche come funzione esterna, ma in questo caso la sua posizione all’interno della classe migliora l’organizzazione dell’API.

Come scegliere tra i tre tipi di metodo

  • Metodo di istanza: usa self e lavora sui dati di un oggetto specifico.
  • Metodo di classe: usa cls e lavora con la classe o crea nuove istanze.
  • Metodo statico: non usa automaticamente né l’istanza né la classe.

Per esempio, il calcolo del totale è un metodo di istanza perché dipende da prezzo e quantità di uno specifico ordine. La creazione da CSV è un metodo di classe perché costruisce un oggetto. Il controllo del codice è statico perché dipende solo dal parametro ricevuto.

Best practice

  • Dai nomi espliciti ai costruttori alternativi: nomi come da_riga_csv, da_json o da_dizionario rendono chiara l’origine dei dati.
  • Usa cls nei metodi di classe: evita di collegare inutilmente il metodo a una classe concreta.
  • Non trasformare ogni funzione in un metodo statico: usalo solo quando la funzione ha un legame concettuale con la classe.
  • Valida i dati all’ingresso: un costruttore alternativo dovrebbe rifiutare formati non validi con eccezioni comprensibili.
  • Mantieni i metodi focalizzati: il parsing dei dati, il calcolo dei totali e la validazione dovrebbero avere responsabilità separate.
  • Documenta i formati supportati: una docstring breve evita ambiguità per chi userà la classe.

Un errore comune consiste nell’usare un metodo statico quando sarebbe più adatto un metodo di classe. Se il metodo deve creare istanze o rispettare il comportamento di una sottoclasse, deve ricevere cls. Al contrario, se non ha bisogno di conoscere la classe, @staticmethod è una scelta più precisa.

Riepilogo

I metodi di classe e i metodi statici permettono di progettare classi più ordinate e riutilizzabili. Il metodo di classe, identificato da @classmethod, riceve la classe nel parametro cls ed è particolarmente utile per creare oggetti attraverso costruttori alternativi.

Il metodo statico, identificato da @staticmethod, è una funzione collegata concettualmente alla classe ma indipendente dalle sue istanze e dai suoi attributi. La scelta corretta dipende dai dati necessari all’operazione: self per l’oggetto, cls per la classe, nessun parametro implicito per un metodo statico.

Approfondisci con risorse ufficiali

SHARE