Python @staticmethod e @classmethod
Aprenda como os decoradores @staticmethod e @classmethod do Python funcionam, quando usar cada um e como criar métodos de fábrica e funções utilitárias com exemplos.
Python atribui a todo método dentro de uma classe um dos três estilos de vinculação: pode estar vinculado a uma instância, à própria classe ou a nenhum dos dois. Os decoradores @classmethod e @staticmethod controlam esses dois últimos estilos.
Este capítulo aborda:
- Os três tipos de métodos e o que os diferencia
@staticmethod— uma função simples armazenada dentro de uma classe@classmethod— um método que recebe a classe como primeiro argumento- Métodos de fábrica: o uso real mais comum do
@classmethod - Construtores alternativos e como eles interagem com herança
- Quando escolher
@staticmethodvs@classmethodvs uma função no nível do módulo - Armadilhas comuns
Antes de ler, certifique-se de estar familiarizado com classes e objetos do Python e herança no Python. Para acesso a atributos computados, consulte @property. Para um aprofundamento em como os decoradores funcionam em geral, veja Decoradores Python.
Os Três Tipos de Métodos
Antes de examinar cada decorador, veja uma comparação lado a lado:
| Método de instância | @classmethod | @staticmethod | |
|---|---|---|---|
| Primeiro parâmetro | self (a instância) | cls (a classe) | nenhum |
| Recebe a instância? | Sim | Não | Não |
| Recebe a classe? | Via type(self) | Sim (diretamente) | Não |
| Chamado em uma instância | Sim | Sim | Sim |
| Chamado na classe | Sim (mas self está ausente) | Sim | Sim |
| Uso típico | Operar sobre dados da instância | Métodos de fábrica, estado no nível da classe | Funções utilitárias/auxiliares |
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
Um método estático é o mais simples dos três. É apenas uma função regular que por acaso reside dentro de um namespace de classe. Python não passa self ou cls automaticamente.
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)) # TrueQuando usar @staticmethod
Use @staticmethod quando um auxiliar pertence logicamente a uma classe — por clareza, agrupamento ou namespacing — mas não precisa ler ou modificar o estado da instância nem o estado da classe:
- Auxiliares de validação chamados antes de construir um objeto.
- Funções puras de conversão ou cálculo que só fazem sentido no contexto de uma classe.
- Funções utilitárias usadas por vários métodos da mesma classe, mas em nenhum outro lugar.
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)) # FalseObserve que _is_valid tem o prefixo _ para sinalizar que é interno à classe. Quem usa apenas objetos Temperature nunca o vê — eles simplesmente recebem um ValueError se passarem um valor impossível.
@staticmethod vs uma função no nível do módulo
Uma função no nível do módulo e um @staticmethod têm comportamento quase idêntico. A diferença está em onde a função reside:
- Se a função for relevante apenas para
Temperature(ou for chamada exclusivamente de dentro deTemperature), coloque-a dentro da classe como um@staticmethod. - Se for um utilitário geral usado em todo o seu módulo, coloque-a no nível do módulo.
Não há diferença de desempenho. Esta é puramente uma escolha organizacional.
@classmethod
Um método de classe recebe a classe como primeiro argumento (chamado cls por convenção — mas assim como self, o nome é uma convenção, não uma palavra-chave). Por ter uma referência à classe, ele pode:
- Ler ou modificar atributos no nível da classe.
- Criar e retornar novas instâncias da classe (métodos de fábrica).
- Funcionar corretamente com subclasses (fábricas polimórficas).
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()) # 0Métodos de fábrica — o caso de uso mais importante
O uso mais comum e valioso do @classmethod é como método de fábrica (também chamado de construtor alternativo). Um método de fábrica cria instâncias a partir de diferentes tipos de entrada sem sobrecarregar __init__ com lógica condicional.
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__ permanece simples — ele apenas armazena três inteiros. Os métodos de classe cuidam da lógica de conversão. Isso é mais limpo do que um único __init__ com múltiplos parâmetros opcionais e ramificações if/elif.
Por que cls é importante para herança
Quando um método de classe de fábrica chama cls(...) em vez de codificar o nome da classe diretamente, ele cria uma instância de qualquer classe em que o método foi chamado — inclusive uma subclasse. É por isso que você deve sempre preferir cls(...) em vez de NomeDaClasse(...) dentro de um @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 tivesse codificado return Date(year, month, day), chamar DateTime.from_string(...) retornaria um Date, não um DateTime — violando silenciosamente o contrato de herança.
Modificando estado no nível da classe
Métodos de classe também podem atuar como construtores nomeados com efeitos colaterais, ou podem manipular variáveis de classe que rastreiam estado compartilhado:
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()) # []Chamando a partir de uma Instância vs da Classe
Tanto @staticmethod quanto @classmethod podem ser chamados tanto em uma instância quanto na classe. Python lida com qualquer uma das formas:
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) # 1Chamá-los diretamente na classe costuma ser mais claro — indica ao leitor que nenhum dado de instância está envolvido.
Combinando @classmethod e @staticmethod
Um método de classe pode delegar trabalho de validação a um método estático, pois o método de classe tem acesso a cls para chamá-lo:
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 -5Referência Rápida: Qual Decorador Devo Usar?
| Situação | Recomendação |
|---|---|
O método lê ou escreve self | Método de instância regular |
| O método cria uma nova instância | @classmethod (fábrica / construtor alternativo) |
| O método lê ou escreve um atributo de classe | @classmethod |
| O método é um auxiliar puro que não precisa de dados de classe ou instância | @staticmethod (ou função no nível do módulo) |
| O método valida entrada antes da construção | @staticmethod |
| O método deve funcionar corretamente em subclasses | @classmethod (use cls, não o nome da classe codificado diretamente) |
Armadilhas Comuns
Esquecer cls dentro de @classmethod
Se você codificar o nome da classe diretamente em vez de usar cls, a herança quebra silenciosamente:
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'> — correctSempre use cls(...), nunca Animal(...), dentro de um método de classe.
Acessar self ou cls em um @staticmethod
Um @staticmethod não recebe nenhum primeiro argumento implícito. Tentar referenciar self ou cls dentro dele é um erro:
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 clsSe você perceber que precisa de cls naquilo que pensou ser um método estático, mude-o para um @classmethod.
Confundindo os decoradores
Métodos @classmethod devem ter cls como primeiro parâmetro explícito, e métodos @staticmethod não devem ter nenhum. Trocá-los causa um TypeError no momento da chamada, não na definição — o que pode ser surpreendente:
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'Sobrescrevendo em subclasses
Ambos os decoradores funcionam com super() e podem ser sobrescritos:
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=ChildObserve que cls em Base.who ainda é Child — porque o método foi despachado a partir de Child.
Exemplo Real: Uma Classe User
Aqui está um exemplo completo que reúne métodos de instância, um método de fábrica de classe e um validador de método estático:
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'Este padrão — __init__ para construção normal, @classmethod para construtores alternativos, @staticmethod para auxiliares — aparece em toda a biblioteca padrão do Python (veja datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).
Resumo
- Um método de instância recebe
selfe tem acesso completo ao estado do objeto. - Um
@classmethodrecebecls— a própria classe — em vez de uma instância. Use-o para métodos de fábrica e qualquer coisa que opere sobre o estado no nível da classe. Sempre usecls(...)dentro dele para que as subclasses funcionem corretamente. - Um
@staticmethodnão recebe nemselfnemcls. Use-o para lógica utilitária pura que pertence ao namespace da classe, mas não precisa de dados do objeto ou da classe.
Para atributos computados que se parecem com acesso regular a atributos, veja @property. Para o mecanismo completo de decoradores que faz os três funcionarem, consulte Decoradores Python.