8 min de leitura
Orientação a Objetos em Python #4 - Atributos e Métodos de Classe
A diferença entre atributos de instância e atributos de classe em Python, o decorador @classmethod e o parâmetro cls, com exemplos práticos de contador de instâncias e alteração de estado compartilhado.
No post anterior, você separou responsabilidades dentro de uma classe usando métodos privados, seguindo o Princípio da Responsabilidade Única. Todos os atributos e métodos que você usou até agora na série, porém, tinham uma coisa em comum: pertenciam a um objeto específico — cada Pessoa tinha seu próprio nome, cada ContaBancaria seu próprio saldo. Mas existe também outro tipo de atributo, que não pertence a nenhum objeto em particular, e sim à classe como um todo, compartilhado por todas as instâncias dela.
Nesta parte da série, você vai entender a diferença entre atributos de instância e atributos de classe, o decorador @classmethod, e vai aplicar tudo isso em um exemplo prático.
Atributos de instância vs. atributos de classe
Um atributo de instância é definido dentro do __init__, usando self., e cada objeto criado a partir da classe recebe sua própria cópia independente — foi o que você viu em toda a série até aqui. Um atributo de classe, por outro lado, é definido diretamente no corpo da classe, fora de qualquer método, e é compartilhado por todos os objetos daquela classe — como se fosse um valor único, acessível tanto pela classe quanto por qualquer instância dela.
class Produto:
imposto = 1.15 # atributo de classe
def __init__(self, valor_bruto):
self.valor_bruto = valor_bruto # atributo de instância
def consultar_valor(self):
valor = self.valor_bruto * self.imposto
print(f"Valor final: R$ {valor:.2f}")
produto_a = Produto(30)
produto_b = Produto(10)
produto_a.consultar_valor()
produto_b.consultar_valor()
A saída é:
Valor final: R$ 34.50
Valor final: R$ 11.50
Repare que valor_bruto é diferente em cada objeto (30 e 10), enquanto imposto é o mesmo valor 1.15 usado pelos dois — nenhum dos dois objetos declarou seu próprio imposto, ambos estão lendo o mesmo valor guardado na classe Produto.
O efeito de alterar um atributo de classe
A parte interessante — e um pouco traiçoeira — acontece quando você altera um atributo de classe depois que já existem objetos criados:
Produto.imposto = 1.20
produto_a.consultar_valor()
produto_b.consultar_valor()
A saída é:
Valor final: R$ 36.00
Valor final: R$ 12.00
Mesmo produto_a e produto_b já existindo antes da mudança, os dois passaram a usar o novo valor de imposto automaticamente. Isso acontece porque nenhum dos dois tem sua própria cópia de imposto — os dois continuam consultando o valor guardado na classe.
Nota: se em vez de alterar via
Produto.impostovocê fizerproduto_a.imposto = 1.30(atribuindo direto numa instância específica), o Python cria uma cópia própria desse atributo só praproduto_a, sem afetarproduto_bnem a classe — é como se aquele objeto “se desligasse” das próximas mudanças no valor compartilhado. As outras instâncias continuam seguindo o valor da classe normalmente. Por isso, quando a intenção é mudar o valor pra todo mundo, o caminho mais confiável é sempre alterar pela classe, não por uma instância específica.
Métodos de classe: @classmethod e cls
Alterar um atributo de classe direto de fora (Produto.imposto = 1.20) funciona, mas expõe demais o funcionamento interno da classe. Uma forma mais controlada é criar um método específico pra isso, usando o decorador @classmethod. A diferença em relação a um método comum é o primeiro parâmetro: em vez de self (que representa uma instância específica), um classmethod recebe cls, que representa a classe como um todo:
class Produto:
imposto = 1.15
def __init__(self, valor_bruto):
self.valor_bruto = valor_bruto
def consultar_valor(self):
valor = self.valor_bruto * self.imposto
print(f"Valor final: R$ {valor:.2f}")
@classmethod
def alterar_imposto(cls, novo_valor):
cls.imposto = novo_valor
produto_a = Produto(30)
produto_b = Produto(10)
Produto.alterar_imposto(1.20)
produto_a.consultar_valor()
produto_b.consultar_valor()
A saída é:
Valor final: R$ 36.00
Valor final: R$ 12.00
cls.imposto = novo_valor tem o mesmo efeito de escrever Produto.imposto = novo_valor, só que de forma encapsulada dentro de um método — quem usa a classe não precisa saber que existe um atributo chamado imposto por trás, só precisa chamar alterar_imposto. E como cls sempre aponta pra classe (nunca pra uma instância isolada), esse método sempre altera o valor compartilhado de verdade, sem risco de cair naquele efeito de “cópia isolada” que aconteceria se você tentasse fazer self.imposto = novo_valor dentro de um método comum.
Um caso de uso prático: contando instâncias
Atributos e métodos de classe são úteis sempre que você precisa de um estado compartilhado entre todos os objetos de uma classe — um exemplo clássico é contar quantas instâncias já foram criadas:
class Produto:
total_criados = 0
def __init__(self, valor_bruto):
self.valor_bruto = valor_bruto
Produto.total_criados += 1
@classmethod
def quantidade_criada(cls):
return cls.total_criados
Produto(30)
Produto(10)
Produto(20)
print(Produto.quantidade_criada())
A saída é:
3
Cada vez que o __init__ roda, ele incrementa Produto.total_criados — um atributo de classe, então o incremento é visto por todos. O classmethod quantidade_criada só devolve esse total, sem precisar de nenhum objeto específico pra ser chamado.
Exercício prático: aplicando tudo numa rede de lojas
Pra fechar, veja um cenário que junta os dois conceitos: imagine uma rede de lojas onde cada loja vende o mesmo tipo de produto, com seu próprio valor bruto, mas todas compartilham a mesma taxa de imposto — e essa taxa pode ser reajustada pra rede inteira de uma vez:
class Loja:
taxa = 1.15 # compartilhada por toda a rede
def __init__(self, valor_produto_bruto):
self.valor_produto_bruto = valor_produto_bruto
def consultar_valor_produto(self):
valor = self.valor_produto_bruto * self.taxa
print(f"Valor do produto: R$ {valor:.2f}")
@classmethod
def editar_taxa(cls, novo_valor):
cls.taxa = novo_valor
loja_praia = Loja(30.50)
loja_shopping = Loja(10.39)
loja_rua = Loja(20.33)
loja_praia.consultar_valor_produto()
loja_shopping.consultar_valor_produto()
loja_rua.consultar_valor_produto()
Loja.editar_taxa(1.35)
loja_praia.consultar_valor_produto()
loja_shopping.consultar_valor_produto()
loja_rua.consultar_valor_produto()
A saída é:
Valor do produto: R$ 35.07
Valor do produto: R$ 11.95
Valor do produto: R$ 23.38
Valor do produto: R$ 41.18
Valor do produto: R$ 14.03
Valor do produto: R$ 27.45
As três lojas foram criadas com seu próprio valor_produto_bruto, mas nenhuma delas tem sua própria cópia de taxa — todas consultam o valor guardado na classe Loja. Quando Loja.editar_taxa(1.35) é chamado, a rede inteira sente o efeito da mudança de uma vez, sem precisar atualizar loja por loja. É esse tipo de situação — um valor que deveria ser sempre o mesmo pra todos os objetos de uma classe, e alterado de forma centralizada — que atributos e métodos de classe resolvem bem.
Até aqui você viu como organizar dados e comportamento dentro de uma única classe, de várias formas diferentes. No próximo post da série, o assunto passa a ser como fazer classes diferentes se relacionarem entre si: Associação de Classes.
Fonte adaptada: Orientação a Objetos em Python (Remake) Aula 7 - Variáveis de Classe, Aula 8 - Métodos de Classe, Aula 9 - Exercício Método de Classe
Comentários