W3docs

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 @staticmethod vs @classmethod vs 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âmetroself (a instância)cls (a classe)nenhum
Recebe a instância?SimNãoNão
Recebe a classe?Via type(self)Sim (diretamente)Não
Chamado em uma instânciaSimSimSim
Chamado na classeSim (mas self está ausente)SimSim
Uso típicoOperar sobre dados da instânciaMétodos de fábrica, estado no nível da classeFunçõ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)) # True

Quando 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))             # False

Observe 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 de Temperature), 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())  # 0

Mé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)  # 1

Chamá-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 -5

Referência Rápida: Qual Decorador Devo Usar?

SituaçãoRecomendação
O método lê ou escreve selfMé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'>  — correct

Sempre 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 cls

Se 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=Child

Observe 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 self e tem acesso completo ao estado do objeto.
  • Um @classmethod recebe cls — 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 use cls(...) dentro dele para que as subclasses funcionem corretamente.
  • Um @staticmethod não recebe nem self nem cls. 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.

Was this page helpful?