W3docs

Python @staticmethod e @classmethod

Scopri come funzionano i decoratori @staticmethod e @classmethod in Python, quando usarli e come scrivere factory method e helper con esempi pratici.

Python assegna a ogni metodo all'interno di una classe uno dei tre stili di binding: può essere associato a un'istanza, alla classe stessa oppure a nessuno dei due. I decoratori @classmethod e @staticmethod controllano questi ultimi due stili.

Questo capitolo tratta:

  • I tre tipi di metodo e le loro differenze
  • @staticmethod — una semplice funzione memorizzata all'interno di una classe
  • @classmethod — un metodo che riceve la classe come primo argomento
  • Factory method: l'uso reale più comune di @classmethod
  • Costruttori alternativi e come interagiscono con l'ereditarietà
  • Quando scegliere @staticmethod vs @classmethod vs una funzione a livello di modulo
  • Errori comuni

Prima di leggere, assicurati di avere familiarità con le classi e gli oggetti Python e con l'ereditarietà in Python. Per l'accesso agli attributi calcolati, consulta @property. Per un approfondimento su come funzionano i decoratori in generale, vedi Decoratori Python.

I Tre Tipi di Metodo

Prima di esaminare ogni decoratore, ecco un confronto affiancato:

Metodo di istanza@classmethod@staticmethod
Primo parametroself (l'istanza)cls (la classe)nessuno
Riceve l'istanza?NoNo
Riceve la classe?Tramite type(self)Sì (direttamente)No
Chiamato su un'istanza
Chiamato sulla classeSì (ma self manca)
Uso tipicoOperare sui dati dell'istanzaFactory method, stato a livello di classeFunzioni di utilità/helper
class Demo:
    def instance_method(self):
        return f"instance method — self is {self}"

    @classmethod
    def class_method(cls):
        return f"class method — cls is {cls}"

    @staticmethod
    def static_method():
        return "static method — no self, no cls"

d = Demo()
print(d.instance_method())   # instance method — self is <__main__.Demo object at 0x...>
print(d.class_method())      # class method — cls is <class '__main__.Demo'>
print(d.static_method())     # static method — no self, no cls

# All three can also be called directly on the class:
print(Demo.class_method())   # class method — cls is <class '__main__.Demo'>
print(Demo.static_method())  # static method — no self, no cls

@staticmethod

Un metodo statico è il più semplice dei tre. È semplicemente una funzione normale che si trova all'interno del namespace di una classe. Python non passa automaticamente self o cls.

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathUtils.add(3, 4))   # 7
print(MathUtils.is_even(10)) # True

Quando usare @staticmethod

Usa @staticmethod quando un helper appartiene logicamente a una classe — per chiarezza, raggruppamento o namespacing — ma non ha bisogno di leggere o modificare né lo stato dell'istanza né quello della classe:

  • Helper di validazione chiamati prima di costruire un oggetto.
  • Funzioni di conversione o calcolo pure che hanno senso solo nel contesto di una classe.
  • Funzioni di utilità usate da diversi metodi della stessa classe ma in nessun altro posto.
class Temperature:
    def __init__(self, celsius):
        if not Temperature._is_valid(celsius):
            raise ValueError(f"Temperature {celsius} °C is below absolute zero")
        self.celsius = celsius

    @staticmethod
    def _is_valid(celsius):
        return celsius >= -273.15

    @staticmethod
    def celsius_to_fahrenheit(celsius):
        return celsius * 9 / 5 + 32

t = Temperature(100)
print(Temperature.celsius_to_fahrenheit(100))  # 212.0
print(Temperature._is_valid(-300))             # False

Nota che _is_valid ha il prefisso _ per segnalare che è interno alla classe. I chiamanti che hanno bisogno solo di oggetti Temperature non lo vedranno mai — riceveranno semplicemente un ValueError se passano un valore impossibile.

@staticmethod vs una funzione a livello di modulo

Una funzione a livello di modulo e un @staticmethod sono quasi identici nel comportamento. La differenza è dove vive la funzione:

  • Se la funzione è rilevante solo per Temperature (o viene chiamata esclusivamente dall'interno di Temperature), inseriscila nella classe come @staticmethod.
  • Se è un'utilità generica usata in tutto il modulo, inseriscila a livello di modulo.

Non c'è differenza di prestazioni. Questa è puramente una scelta organizzativa.

@classmethod

Un metodo di classe riceve la classe come primo argomento (chiamato cls per convenzione — ma proprio come self, il nome è una convenzione, non una parola chiave). Poiché ha un riferimento alla classe, può:

  • Leggere o modificare attributi a livello di classe.
  • Creare e restituire nuove istanze della classe (factory method).
  • Funzionare correttamente con le sottoclassi (factory polimorfiche).
class Counter:
    _count = 0  # class-level attribute

    def __init__(self):
        Counter._count += 1

    @classmethod
    def get_count(cls):
        return cls._count

    @classmethod
    def reset(cls):
        cls._count = 0

Counter()
Counter()
Counter()
print(Counter.get_count())  # 3
Counter.reset()
print(Counter.get_count())  # 0

Factory method — il caso d'uso più importante

L'uso più comune e prezioso di @classmethod è come factory method (detto anche costruttore alternativo). Un factory method crea istanze a partire da diversi tipi di input senza appesantire __init__ con logica condizionale.

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        """Create a Date from an ISO 8601 string, e.g. '2024-03-15'."""
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)

    @classmethod
    def from_tuple(cls, date_tuple):
        """Create a Date from a (year, month, day) tuple."""
        return cls(*date_tuple)

d1 = Date(2024, 3, 15)
d2 = Date.from_string("2024-03-15")
d3 = Date.from_tuple((2024, 3, 15))

print(d1)  # Date(2024, 3, 15)
print(d2)  # Date(2024, 3, 15)
print(d3)  # Date(2024, 3, 15)

__init__ rimane semplice — memorizza solo tre interi. I metodi di classe gestiscono la logica di conversione. Questo è più pulito di un singolo __init__ con più parametri opzionali e rami if/elif.

Perché cls è importante per l'ereditarietà

Quando un factory class method chiama cls(...) invece di codificare a parte il nome della classe, crea un'istanza di qualunque classe su cui il metodo è stato chiamato — anche una sottoclasse. Per questo dovresti sempre preferire cls(...) a ClassName(...) all'interno di un @classmethod.

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)  # uses cls, not Date


class DateTime(Date):
    pass  # inherits from_string


dt = DateTime.from_string("2024-03-15")
print(dt)           # DateTime(2024, 3, 15)  — correct subclass
print(type(dt))     # <class '__main__.DateTime'>

Se from_string avesse codificato a parte return Date(year, month, day), chiamare DateTime.from_string(...) restituirebbe un Date, non un DateTime — rompendo silenziosamente il contratto di ereditarietà.

Modifica dello stato a livello di classe

I metodi di classe possono anche agire come costruttori con nome ed effetti collaterali, oppure manipolare variabili di classe che tracciano uno stato condiviso:

class Registry:
    _instances = []

    def __init__(self, name):
        self.name = name
        Registry._instances.append(self)

    @classmethod
    def all(cls):
        return list(cls._instances)

    @classmethod
    def clear(cls):
        cls._instances.clear()

Registry("alice")
Registry("bob")
Registry("carol")
print([r.name for r in Registry.all()])  # ['alice', 'bob', 'carol']
Registry.clear()
print(Registry.all())                    # []

Chiamata da un'Istanza vs dalla Classe

Sia @staticmethod che @classmethod possono essere chiamati su un'istanza o sulla classe. Python gestisce entrambe le forme:

class Circle:
    PI = 3.14159265

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return Circle.PI * self.radius ** 2

    @classmethod
    def unit_circle(cls):
        """Return a circle with radius 1."""
        return cls(1)

    @staticmethod
    def describe():
        return "A circle is a round plane figure."

c = Circle(5)

# staticmethod — callable on instance or class
print(c.describe())          # A circle is a round plane figure.
print(Circle.describe())     # A circle is a round plane figure.

# classmethod — callable on instance or class
unit = c.unit_circle()
print(unit.radius)           # 1
print(Circle.unit_circle().radius)  # 1

Chiamarli sulla classe è di solito più chiaro — segnala al lettore che non sono coinvolti dati dell'istanza.

Combinare @classmethod e @staticmethod

Un metodo di classe può delegare il lavoro di validazione a un metodo statico, poiché il metodo di classe ha accesso a cls per chiamarlo:

class PositiveNumber:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"PositiveNumber({self.value})"

    @staticmethod
    def _validate(value):
        if value <= 0:
            raise ValueError(f"Expected a positive number, got {value!r}")

    @classmethod
    def create(cls, value):
        cls._validate(value)
        return cls(value)

n = PositiveNumber.create(42)
print(n)  # PositiveNumber(42)

try:
    PositiveNumber.create(-5)
except ValueError as e:
    print(e)  # Expected a positive number, got -5

Riferimento Rapido: Quale Decoratore Usare?

SituazioneRaccomandazione
Il metodo legge o scrive selfMetodo di istanza normale
Il metodo crea una nuova istanza@classmethod (factory / costruttore alternativo)
Il metodo legge o scrive un attributo di classe@classmethod
Il metodo è un helper puro che non necessita di dati di classe o istanza@staticmethod (o funzione a livello di modulo)
Il metodo valida l'input prima della costruzione@staticmethod
Il metodo deve funzionare correttamente nelle sottoclassi@classmethod (usa cls, non il nome della classe codificato a parte)

Errori Comuni

Dimenticare cls dentro @classmethod

Se si codifica a parte il nome della classe invece di usare cls, l'ereditarietà si rompe silenziosamente:

class Animal:
    @classmethod
    def create(cls):
        return cls()          # correct — returns an instance of the actual class

class Dog(Animal):
    pass

print(type(Dog.create()))     # <class '__main__.Dog'>  — correct

Usa sempre cls(...), mai Animal(...), all'interno di un metodo di classe.

Accedere a self o cls in un @staticmethod

Un @staticmethod non riceve alcun primo argomento implicito. Tentare di fare riferimento a self o cls al suo interno è un errore:

class Bad:
    label = "bad"

    @staticmethod
    def show():
        # print(cls.label)  # NameError: name 'cls' is not defined
        print("use @classmethod if you need cls")

Bad.show()  # use @classmethod if you need cls

Se ti accorgi di aver bisogno di cls in quello che pensavi fosse un metodo statico, convertilo in un @classmethod.

Confondere i decoratori

I metodi @classmethod devono avere cls come primo parametro esplicito, e i metodi @staticmethod non devono averne nessuno. Scambiarli causa un TypeError al momento della chiamata, non alla definizione — il che può sorprendere:

class Broken:
    @staticmethod
    def forgot_cls(cls):   # cls is just a regular positional argument here
        return cls

# Broken.forgot_cls()  # TypeError: forgot_cls() missing 1 required positional argument: 'cls'

Override nelle sottoclassi

Entrambi i decoratori funzionano con super() e possono essere sovrascritti:

class Base:
    @classmethod
    def who(cls):
        return f"Base.who called with cls={cls.__name__}"

class Child(Base):
    @classmethod
    def who(cls):
        parent = super().who()
        return f"Child.who — parent said: {parent}"

print(Child.who())
# Child.who — parent said: Base.who called with cls=Child

Nota che cls in Base.who è ancora Child — perché il metodo è stato chiamato da Child.

Esempio Pratico: Una Classe User

Ecco un esempio completo che unisce metodi di istanza, un factory class method e un validatore metodo statico:

import re

class User:
    _all_users = []

    def __init__(self, name, email):
        User._validate_email(email)
        self.name = name
        self.email = email
        User._all_users.append(self)

    def __repr__(self):
        return f"User(name={self.name!r}, email={self.email!r})"

    # --- instance method ---
    def greet(self):
        return f"Hello, my name is {self.name}."

    # --- factory / alternative constructor ---
    @classmethod
    def from_dict(cls, data):
        """Create a User from a dict like {'name': 'Alice', 'email': '[email protected]'}."""
        return cls(data["name"], data["email"])

    # --- class-level query ---
    @classmethod
    def count(cls):
        return len(cls._all_users)

    # --- pure helper, no instance or class data needed ---
    @staticmethod
    def _validate_email(email):
        pattern = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$"
        if not re.match(pattern, email):
            raise ValueError(f"Invalid email address: {email!r}")

# Create via normal constructor
u1 = User("Alice", "[email protected]")

# Create via factory
u2 = User.from_dict({"name": "Bob", "email": "[email protected]"})

print(u1.greet())    # Hello, my name is Alice.
print(u2.greet())    # Hello, my name is Bob.
print(User.count())  # 2

try:
    User("Carol", "not-an-email")
except ValueError as e:
    print(e)         # Invalid email address: 'not-an-email'

Questo pattern — __init__ per la costruzione normale, @classmethod per costruttori alternativi, @staticmethod per gli helper — appare in tutta la libreria standard Python (vedi datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).

  • Un metodo di istanza riceve self e ha pieno accesso allo stato dell'oggetto.
  • Un @classmethod riceve cls — la classe stessa — invece di un'istanza. Usalo per i factory method e per tutto ciò che opera sullo stato a livello di classe. Usa sempre cls(...) al suo interno in modo che le sottoclassi funzionino correttamente.
  • Un @staticmethod non riceve né selfcls. Usalo per logica di utilità pura che appartiene al namespace della classe ma non necessita di dati dell'oggetto o della classe.

Per gli attributi calcolati che sembrano un normale accesso agli attributi, vedi @property. Per il meccanismo completo dei decoratori che fa funzionare tutti e tre, vedi Decoratori Python.

Was this page helpful?