Python @classmethod e @staticmethod

Até aqui, todo método que você escreveu começava com self: ele trabalha com um objeto (este café, aquele aluno). Mas algumas tarefas pertencem à classe sem pertencer a nenhum objeto em especial. "Quantos produtos a lojinha já cadastrou?" é uma pergunta sobre a classe Produto, não sobre o café. "Transforme o número 1234.5 no texto R$ 1.234,50" nem precisa de produto nenhum. Para esses casos, o Python tem dois tipos de método: o método de classe, marcado com @classmethod, e o método estático, marcado com @staticmethod.

Você vai aprender
  • A diferença entre os três tipos de método: o normal (self), o de classe (cls) e o estático (nenhum dos dois).
  • O que é a "arroba" (@) em cima do def.
  • A usar um método de classe para contar os objetos criados.
  • O uso mais comum do @classmethod: um segundo jeito de criar objetos, como Produto.de_texto("Café;18.90").
  • Por que escrever cls(...) e não Produto(...) dentro dele (e o defeito silencioso que aparece com herança).
  • Quando um @staticmethod faz sentido e quando uma função comum é melhor.
  • Os erros reais: missing 1 required positional argument: 'self', takes 1 positional argument but 2 were given e companhia.

A ideia: a fábrica, o bolo e a balança

Pense numa fábrica de bolos. A receita é a classe; cada bolo que sai do forno é um objeto. Agora repare em três perguntas diferentes:

  • "Qual é o sabor deste bolo?" Só dá para responder olhando um bolo. É um método normal: recebe o bolo (o self).
  • "Quantos bolos a fábrica já fez hoje?" "Faça um bolo a partir deste pedido escrito num papel." São tarefas da fábrica, não de um bolo. É um método de classe: recebe a classe (o cls).
  • "Quanto são 250 gramas em xícaras?" A balança da cozinha responde sem olhar bolo nenhum e sem saber nada da fábrica. Ela só fica guardada ali porque é coisa de cozinha. É um método estático: não recebe nem o bolo nem a fábrica.
Tipo de métodoMarcaPrimeiro parâmetroTrabalha com…
Normal (de instância)nenhumaself (o objeto)os dados de um objeto
De classe@classmethodcls (a classe)a classe inteira: contadores, outros jeitos de criar objetos
Estático@staticmethodnenhum especialsó com os argumentos que recebe

1. Lembrando: o método normal recebe self

A lojinha da Ana tem uma classe Produto, com o nome e o preço de cada produto, como nas aulas __init__ e self e Atributos e Métodos:

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


cafe = Produto("Café", 18.9)
print(cafe.descricao())
$ python3 normal.py
Café: R$ 18.90

Quando você escreve cafe.descricao(), o Python entrega o próprio cafe no parâmetro self. É por isso que descricao consegue ler self.nome: ela sabe de qual produto está falando. Guarde isso, porque os outros dois tipos de método mudam justamente o que chega no primeiro parâmetro.


2. O primeiro @classmethod: o contador da lojinha

A Ana quer saber quantos produtos já foram cadastrados. Esse número não é de um produto; é da classe. Ele fica num atributo da classe (escrito dentro da classe, fora de qualquer método), e quem o mostra é um método de classe:

class Produto:
    criados = 0  # atributo da classe: um só, para todos os produtos

    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco
        Produto.criados += 1

    @classmethod
    def total(cls):
        return f"A lojinha já cadastrou {cls.criados} produtos."


print(Produto.total())
cafe = Produto("Café", 18.9)
leite = Produto("Leite", 5.49)
print(Produto.total())
print(cafe.total())
criados = 0
Um atributo da classe. Existe uma cópia só, que todos os produtos compartilham, ao contrário de self.nome, que cada produto tem o seu.
Produto.criados += 1
Toda vez que um produto nasce, o __init__ soma 1 ao contador da classe.
@classmethod
Um decorador (do inglês decorator, "enfeite"): uma linha que começa com @, fica logo acima do def e muda o jeito como aquele método funciona. Este diz: "este é um método de classe". Decoradores têm aula própria, Decoradores; aqui basta saber usar estes dois.
def total(cls):
O primeiro parâmetro agora se chama cls (abreviação de class, "classe"). Nele o Python entrega a classe, e não um objeto. O nome cls é só costume, como self; mas é o costume que todo mundo segue.
cls.criados
Lê o atributo da classe através do cls.
Produto.total()
Chama o método direto pela classe, sem precisar de nenhum objeto.
$ python3 contador.py
A lojinha já cadastrou 0 produtos.
A lojinha já cadastrou 2 produtos.
A lojinha já cadastrou 2 produtos.

Três coisas para reparar:

  1. A primeira linha saiu antes de existir qualquer produto. Um método normal não conseguiria isso: sem objeto, não há self.
  2. Depois do café e do leite, o contador foi para 2.
  3. cafe.total() também funciona, e dá o mesmo resultado. Mesmo chamado por um objeto, o cls recebe a classe do objeto, nunca o objeto. O costume, porém, é chamar pela classe (Produto.total()), que deixa claro que a pergunta é sobre a lojinha toda.

O que chega no cls, afinal?

Dá para conferir com um print:

class Produto:
    @classmethod
    def mostrar(cls):
        print("cls é:", cls)
        print("cls é Produto?", cls is Produto)


Produto.mostrar()
$ python3 cls.py
cls é: <class '__main__.Produto'>
cls é Produto? True

<class '__main__.Produto'> é como o Python mostra uma classe: "a classe Produto, criada no programa principal" (__main__ é o nome que o Python dá ao arquivo que você mandou rodar). E cls is Produto deu True: dentro do método, cls é a própria classe Produto.


3. O uso mais comum: outro jeito de criar objetos

A Ana recebe a lista de preços do fornecedor num arquivo de texto, uma linha por produto, assim: Arroz 5 kg;27.50. Para criar o produto, ela teria de separar o texto, converter o preço e só então chamar Produto(...), toda vez. Fica muito melhor ensinar a própria classe a fazer isso:

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    @classmethod
    def de_texto(cls, linha):
        nome, preco = linha.split(";")
        return cls(nome, float(preco))

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


cafe = Produto("Café", 18.9)
arroz = Produto.de_texto("Arroz 5 kg;27.50")
print(cafe.descricao())
print(arroz.descricao())
print(type(arroz))
def de_texto(cls, linha):
Recebe a classe (cls) e a linha de texto.
nome, preco = linha.split(";")
Corta o texto no ponto e vírgula e guarda as duas partes (aulas Métodos de Texto e Desempacotamento).
cls(nome, float(preco))
Como cls é a classe, cls(...) é o mesmo que Produto(...): cria um produto novo, passando pelo __init__ de sempre.
return
Devolve o produto pronto.
$ python3 de_texto.py
Café: R$ 18.90
Arroz 5 kg: R$ 27.50
<class '__main__.Produto'>

Agora a classe tem duas portas de entrada: Produto("Café", 18.9), quando você já tem o nome e o preço separados, e Produto.de_texto("Arroz 5 kg;27.50"), quando tem a linha do fornecedor. Os dois caminhos terminam no mesmo __init__ e dão o mesmo tipo de objeto. Um método assim se chama construtor alternativo.

O próprio Python faz assim

Você já pode ter usado construtores alternativos sem saber. Eles costumam ter nomes que começam com from ("a partir de"):

from datetime import date

estoque = dict.fromkeys(["café", "leite", "pão"], 0)
print(estoque)

aniversario = date.fromisoformat("2026-03-12")
print(aniversario)
print(type(aniversario))

print(int.from_bytes(b"\x01\x00", "big"))
$ python3 do_python.py
{'café': 0, 'leite': 0, 'pão': 0}
2026-03-12
<class 'datetime.date'>
256

dict.fromkeys cria um dicionário "a partir de uma lista de chaves"; date.fromisoformat cria uma data "a partir de um texto no formato ano-mês-dia" (aula Datas e Horas); int.from_bytes cria um número "a partir de bytes". Todos são métodos de classe: você chama pela classe (dict, date, int) e recebe um objeto novo. Nos seus programas, em português, nomes como de_texto, de_dicionario e de_linha_csv seguem a mesma ideia.

Missão: o programa chama Produto.de_texto(...), mas o método ainda não existe (rode para ver o erro). Crie o método de classe de_texto, que recebe "Feijão;8.79" e devolve um Produto.

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    # crie aqui o método de classe de_texto

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


feijao = Produto.de_texto("Feijão;8.79")
print(feijao.descricao())
class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    @classmethod
    def de_texto(cls, linha):
        nome, preco = linha.split(";")
        return cls(nome, float(preco))

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


feijao = Produto.de_texto("Feijão;8.79")
print(feijao.descricao())

4. Por que cls(...), e não Produto(...)?

Dentro de de_texto, escrever Produto(nome, float(preco)) parece dar no mesmo. E dá, até o dia em que aparece uma classe filha (aula Herança). A Ana criou a classe Importado, que herda tudo de Produto e só muda a descrição. Veja a diferença entre usar cls e escrever o nome da classe à mão:

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"

    @classmethod
    def de_texto(cls, linha):
        nome, preco = linha.split(";")
        return cls(nome, float(preco))

    @classmethod
    def de_texto_fixo(cls, linha):
        nome, preco = linha.split(";")
        return Produto(nome, float(preco))  # nome da classe escrito à mão


class Importado(Produto):
    def descricao(self):
        return f"{self.nome} (importado): R$ {self.preco:.2f}"


azeite = Importado.de_texto("Azeite;42.00")
print(type(azeite).__name__)
print(azeite.descricao())

vinho = Importado.de_texto_fixo("Vinho;59.90")
print(type(vinho).__name__)
print(vinho.descricao())
$ python3 heranca.py
Importado
Azeite (importado): R$ 42.00
Produto
Vinho: R$ 59.90

Os dois métodos foram chamados pela classe Importado. Mas:

  • de_texto usa cls. Chamado por Importado, o cls é Importado, e o azeite nasceu importado, com a descrição certa.
  • de_texto_fixo escreve Produto à mão. Não importa por qual classe foi chamado: sempre cria um Produto comum. O vinho perdeu o "(importado)" e nenhum erro apareceu. É o pior tipo de defeito, o que passa calado.

(type(azeite).__name__ mostra só o nome da classe do objeto, sem o <class '__main__....'> em volta.)

Regra prática

Dentro de um @classmethod, fale sempre da classe pelo cls: cls(...) para criar, cls.criados para ler um atributo da classe. Assim o método continua certo nas classes filhas.


5. O @staticmethod: uma ferramenta guardada na classe

Algumas funções têm tudo a ver com produtos, mas não precisam de produto nenhum para trabalhar: "este preço é válido?", "como fica este número em reais?". Elas recebem um valor e devolvem uma resposta. Para guardá-las dentro da classe, use @staticmethod ("método estático"):

class Produto:
    def __init__(self, nome, preco):
        if not Produto.preco_valido(preco):
            raise ValueError(f"preço inválido: {preco}")
        self.nome = nome
        self.preco = preco

    @staticmethod
    def preco_valido(valor):
        return valor > 0

    @staticmethod
    def em_reais(valor):
        texto = f"{valor:,.2f}"
        return "R$ " + texto.replace(",", "X").replace(".", ",").replace("X", ".")


print(Produto.preco_valido(18.9))
print(Produto.preco_valido(-3))
print(Produto.em_reais(1234.5))
cafe = Produto("Café", 18.9)
print(cafe.em_reais(cafe.preco))
@staticmethod
Diz: "este método não recebe nada automaticamente". Nem o objeto, nem a classe.
def preco_valido(valor):
Sem self e sem cls. O valor é um parâmetro comum, que você passa na chamada.
return valor > 0
Devolve True ou False.
f"{valor:,.2f}"
Formata com vírgula separando os milhares e duas casas decimais: 1,234.50, o jeito americano (aula Formatação).
.replace(",", "X")…
Troca a vírgula pelo ponto e o ponto pela vírgula, passando por um X provisório para uma troca não desfazer a outra: 1.234,50.
Produto.preco_valido(preco)
Dentro do __init__, o método estático é chamado pela classe, como qualquer outro.
$ python3 estatico.py
True
False
R$ 1.234,50
R$ 18,90

O método estático funciona chamado pela classe (Produto.em_reais(1234.5)) ou por um objeto (cafe.em_reais(cafe.preco)). Nos dois casos, ele recebe só o que está entre os parênteses. Repare que, mesmo chamado pelo café, foi preciso passar cafe.preco: o método não enxerga o café.

Missão: a Ana guardou em_reais dentro da classe, mas esqueceu uma linha, e o programa dá TypeError. Rode, leia o erro e conserte.

class Produto:
    def em_reais(valor):
        return f"R$ {valor:.2f}"


cafe = Produto()
print(cafe.em_reais(18.9))
print(Produto.em_reais(5.49))
class Produto:
    @staticmethod
    def em_reais(valor):
        return f"R$ {valor:.2f}"


cafe = Produto()
print(cafe.em_reais(18.9))
print(Produto.em_reais(5.49))

6. Os três lado a lado

class Produto:
    def normal(self):
        return "normal"

    @classmethod
    def da_classe(cls):
        return "da classe"

    @staticmethod
    def estatico():
        return "estático"


p = Produto()
print(p.normal(), p.da_classe(), p.estatico())
print(Produto.da_classe(), Produto.estatico())
$ python3 tres.py
normal da classe estático
da classe estático
Normal@classmethod@staticmethod
O Python entrega sozinho…o objeto, em selfa classe, em clsnada
Lê os dados do objeto (self.nome)?SimNãoNão
Lê os atributos da classe?SimSim, por clsSó escrevendo o nome da classe
Chamar pelo objeto (p.metodo())SimSimSim
Chamar pela classe (Produto.metodo())Não (falta o self)SimSim
Uso típicoquase tudoconstrutores alternativos, contadoresfunções utilitárias ligadas ao assunto da classe

Formas diferentes de fazer a mesma coisa

Método estático ou função comum?

Um @staticmethod é, na prática, uma função comum que mora dentro da classe. Muitas vezes, uma função solta no arquivo resolve igual:

def em_reais(valor):
    return f"R$ {valor:.2f}"


class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    def descricao(self):
        return f"{self.nome}: {em_reais(self.preco)}"


print(em_reais(7.5))
print(Produto("Pão de queijo", 7.5).descricao())
$ python3 funcao_solta.py
R$ 7.50
Pão de queijo: R$ 7.50

Muitos programadores Python preferem essa forma: a função fica disponível para o programa todo (inclusive para quem não tem nada a ver com produtos). O @staticmethod vale a pena quando a função só faz sentido junto da classe, e você quer que quem lê Produto.preco_valido(...) entenda na hora que é uma regra dos produtos.

Construtor alternativo ou um __init__ que aceita tudo?

Outra forma seria um __init__ que testa se recebeu um texto ou dois valores, cheio de if. Funciona, mas fica confuso: Produto("Arroz;27.50") não diz o que está acontecendo. Produto.de_texto("Arroz;27.50") diz. Prefira um __init__ simples e um método de classe para cada formato diferente.

Você precisa…Use
Trabalhar com os dados de um objeto (self.preco)Método normal
Criar objetos a partir de outro formato (texto, dicionário, linha de CSV)@classmethod que termina em return cls(...)
Ler ou mudar algo da classe inteira (um contador)@classmethod
Uma regra ou conta que só usa os argumentos e só faz sentido com a classe@staticmethod
Uma conta útil para o programa todoFunção comum, fora da classe

Quando dá errado

Chamar um método normal pela classe
class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    def descricao(self):
        return f"{self.nome}: R$ {self.preco:.2f}"


print(Produto.descricao())
$ python3 erro_self.py
Traceback (most recent call last):
  File "/home/ana/erro_self.py", line 10, in <module>
    print(Produto.descricao())
          ~~~~~~~~~~~~~~~~~^^
TypeError: Produto.descricao() missing 1 required positional argument: 'self'

Linha por linha:

  • Traceback (most recent call last): "rastro (a chamada mais recente por último)". O Python vai mostrar o caminho até o erro.
  • File "/home/ana/erro_self.py", line 10, in <module>: o erro aconteceu na linha 10, no corpo principal do arquivo (<module>).
  • Os ~~~^^ embaixo da linha apontam a chamada que falhou: Produto.descricao().
  • TypeError: Produto.descricao() missing 1 required positional argument: 'self': "erro de tipo: está faltando 1 argumento obrigatório, o self".

Chamado pela classe, o método normal não sabe de qual produto deve falar, e ninguém preencheu o self. Como consertar: crie um objeto e chame por ele (cafe.descricao()). Se o método realmente não precisa de objeto, ele deveria ser @classmethod ou @staticmethod.

Esquecer o @staticmethod
class Produto:
    def em_reais(valor):
        return f"R$ {valor:.2f}"


cafe = Produto()
print(cafe.em_reais(18.9))
$ python3 erro_static.py
Traceback (most recent call last):
  File "/home/ana/erro_static.py", line 7, in <module>
    print(cafe.em_reais(18.9))
          ~~~~~~~~~~~~~^^^^^^
TypeError: Produto.em_reais() takes 1 positional argument but 2 were given

"em_reais() recebe 1 argumento, mas foram passados 2." Você só escreveu um (18.9)! O segundo é o objeto: sem decorador, em_reais é um método normal, e o Python entregou o cafe no primeiro parâmetro, o valor. Não sobrou lugar para o 18.9. Como consertar: ponha @staticmethod na linha de cima do def. (Foi a missão do item 5.)

@classmethod sem o cls
class Produto:
    criados = 0

    @classmethod
    def total():
        return Produto.criados


print(Produto.total())
$ python3 erro_cls.py
Traceback (most recent call last):
  File "/home/ana/erro_cls.py", line 9, in <module>
    print(Produto.total())
          ~~~~~~~~~~~~~^^
TypeError: Produto.total() takes 0 positional arguments but 1 was given

O mesmo problema, ao contrário: o @classmethod sempre entrega a classe no primeiro parâmetro, mas total() não tem parâmetro nenhum para recebê-la. "Recebe 0, foi passado 1." Como consertar: def total(cls):.

Querer os dados de um objeto dentro de um @classmethod
class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    @classmethod
    def descricao(cls):
        return f"{cls.nome}: R$ {cls.preco:.2f}"


cafe = Produto("Café", 18.9)
print(cafe.descricao())
$ python3 erro_cls_nome.py
Traceback (most recent call last):
  File "/home/ana/erro_cls_nome.py", line 12, in <module>
    print(cafe.descricao())
          ~~~~~~~~~~~~~~^^
  File "/home/ana/erro_cls_nome.py", line 8, in descricao
    return f"{cls.nome}: R$ {cls.preco:.2f}"
              ^^^^^^^^
AttributeError: type object 'Produto' has no attribute 'nome'

Agora o rastro tem dois andares: a linha 12 chamou descricao, e o erro estourou lá dentro, na linha 8, em cls.nome. "O objeto-tipo Produto não tem o atributo nome." Mesmo chamado pelo café, o cls é a classe, e o nome mora em cada objeto (self.nome), não na classe. Como consertar: um método que usa os dados de um objeto é um método normal: tire o @classmethod e troque cls por self.

Usar self dentro de um @staticmethod
class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

    @staticmethod
    def descricao():
        return f"{self.nome}: R$ {self.preco:.2f}"


cafe = Produto("Café", 18.9)
print(cafe.descricao())
$ python3 erro_static_self.py
Traceback (most recent call last):
  File "/home/ana/erro_static_self.py", line 12, in <module>
    print(cafe.descricao())
          ~~~~~~~~~~~~~~^^
  File "/home/ana/erro_static_self.py", line 8, in descricao
    return f"{self.nome}: R$ {self.preco:.2f}"
              ^^^^
NameError: name 'self' is not defined

"O nome self não está definido." O self não é uma palavra mágica: é só um parâmetro, e o método estático não tem esse parâmetro. Como consertar: se precisa do objeto, é um método normal (def descricao(self):, sem o decorador).


Referência rápida

EscritaO que faz
def metodo(self):Método normal: recebe o objeto
@classmethod
def metodo(cls):
Método de classe: recebe a classe
@staticmethod
def metodo(x):
Método estático: recebe só os argumentos
return cls(a, b)Dentro de um @classmethod: cria um objeto da classe que chamou (funciona com classes filhas)
Classe.metodo()Chama um método de classe ou estático sem criar objeto
dict.fromkeys(chaves, 0), date.fromisoformat(texto)Construtores alternativos do próprio Python
SintomaCausa provável
missing 1 required positional argument: 'self'Método normal chamado pela classe, sem objeto
takes 1 positional argument but 2 were givenFaltou o @staticmethod (ou faltou o self num método normal)
takes 0 positional arguments but 1 was givenMétodo de classe sem o parâmetro cls
type object 'Produto' has no attribute 'nome'Usou cls.nome: o nome é de cada objeto, não da classe
name 'self' is not definedUsou self num método estático
A classe filha cria objetos da classe mãe, sem erroO construtor alternativo escreveu o nome da classe em vez de cls

Exercícios

1. A marca

Complete o decorador que transforma total num método de classe:


def total(cls):

2. O primeiro parâmetro

Complete o primeiro parâmetro do construtor alternativo e a criação do objeto:

@classmethod
def de_texto(, linha):
    nome, preco = linha.split(";")
    return (nome, float(preco))

3. Num método marcado com @classmethod, o que o Python entrega no primeiro parâmetro quando você escreve cafe.total()?

4. A função converte_kg_em_gramas(kg) só faz uma conta com o número que recebe, e só é usada pelos produtos. Qual é a escolha mais adequada?

5. cafe.em_reais(18.9) deu takes 1 positional argument but 2 were given. O que aconteceu?

6. Por que, dentro de um construtor alternativo, é melhor escrever return cls(...) do que return Produto(...)?

Missão final: o boletim da turma

Missão: a professora da turma da Ana recebe as notas em linhas como "Bia;8.5". Complete a classe Aluno com: (1) um método estático situacao(nota), que devolve "aprovado" se a nota for 7 ou mais e "recuperação" se for menor; (2) um método de classe de_texto(cls, linha), que cria o aluno a partir da linha.

class Aluno:
    matriculados = 0

    def __init__(self, nome, nota):
        self.nome = nome
        self.nota = nota
        Aluno.matriculados += 1

    # 1) crie aqui o método estático situacao(nota)

    # 2) crie aqui o método de classe de_texto(cls, linha)

    def boletim(self):
        return f"{self.nome}: {self.nota} ({Aluno.situacao(self.nota)})"


for linha in ["Bia;8.5", "Caio;6.0", "Duda;9.25"]:
    aluno = Aluno.de_texto(linha)
    print(aluno.boletim())

print(Aluno.matriculados, "alunos")
class Aluno:
    matriculados = 0

    def __init__(self, nome, nota):
        self.nome = nome
        self.nota = nota
        Aluno.matriculados += 1

    @staticmethod
    def situacao(nota):
        if nota >= 7:
            return "aprovado"
        return "recuperação"

    @classmethod
    def de_texto(cls, linha):
        nome, nota = linha.split(";")
        return cls(nome, float(nota))

    def boletim(self):
        return f"{self.nome}: {self.nota} ({Aluno.situacao(self.nota)})"


for linha in ["Bia;8.5", "Caio;6.0", "Duda;9.25"]:
    aluno = Aluno.de_texto(linha)
    print(aluno.boletim())

print(Aluno.matriculados, "alunos")

Com a solução, o lab mostrou:

$ python3 main.py
Bia: 8.5 (aprovado)
Caio: 6.0 (recuperação)
Duda: 9.25 (aprovado)
3 alunos

Resumo

  • O método normal recebe o objeto em self e trabalha com os dados dele.
  • O @classmethod recebe a classe em cls, seja chamado pela classe ou por um objeto. Serve para contadores e, principalmente, para construtores alternativos (Produto.de_texto(...), como dict.fromkeys e date.fromisoformat).
  • Dentro do @classmethod, crie com cls(...), não com o nome da classe: assim as classes filhas recebem objetos do tipo certo.
  • O @staticmethod não recebe nada automaticamente: é uma função comum guardada na classe. Se ela serve para o programa todo, uma função fora da classe pode ser melhor.
  • Os erros contam o que faltou ou sobrou no primeiro parâmetro: missing … 'self', takes 1 … but 2 were given, takes 0 … but 1 was given.