Atributos de classe e instância
Esta aula explica a diferença entre atributos de classe e de instância em Python, mostrando como declarar cada um, o comportamento de variáveis mutáveis compartilhadas e as armadilhas comuns. Inclui exemplos práticos e exercícios para fixação.
Em Python, atributos são variáveis associadas a objetos. Eles podem pertencer à classe (compartilhados por todas as instâncias) ou a cada instância individualmente. Entender essa diferença é fundamental para projetar classes corretas e evitar bugs sutis.
Atributos de classe são definidos diretamente no corpo da classe, fora de qualquer método, e são acessíveis tanto pela classe quanto por suas instâncias. Já atributos de instância são criados dentro do método __init__ (ou outros métodos) e pertencem exclusivamente a cada objeto.
Diferença
A principal diferença está no escopo e no momento de criação. Atributos de classe são criados quando a classe é definida e existem uma única vez, independentemente do número de instâncias. Atributos de instância são criados quando o objeto é instanciado e cada objeto tem sua própria cópia.
Além disso, ao modificar um atributo de classe através de uma instância, você na verdade cria um novo atributo de instância que sombreia o da classe. Isso pode gerar confusão se não for compreendido.
class Exemplo:
atributo_classe = 0 # atributo de classe
def __init__(self, valor):
self.atributo_instancia = valor # atributo de instância
obj1 = Exemplo(10)
obj2 = Exemplo(20)
print(obj1.atributo_classe) # 0
print(obj2.atributo_classe) # 0
print(obj1.atributo_instancia) # 10
print(obj2.atributo_instancia) # 20
# Modificando via classe
Exemplo.atributo_classe = 100
print(obj1.atributo_classe) # 100
print(obj2.atributo_classe) # 100
# Modificando via instância - cria atributo de instância
obj1.atributo_classe = 50
print(obj1.atributo_classe) # 50 (instância)
print(obj2.atributo_classe) # 100 (classe)
print(Exemplo.atributo_classe) # 100Atributos de classe
Atributos de classe são úteis para valores que devem ser compartilhados entre todas as instâncias, como constantes, contadores ou configurações globais da classe. Eles podem ser acessados diretamente pelo nome da classe ou por uma instância (desde que não haja sombreamento).
Exemplo comum: um contador de instâncias criadas.
class Pessoa:
total_pessoas = 0 # atributo de classe
def __init__(self, nome):
self.nome = nome
Pessoa.total_pessoas += 1
p1 = Pessoa("Ana")
p2 = Pessoa("João")
print(Pessoa.total_pessoas) # 2Outro uso é para definir valores padrão que podem ser sobrescritos em instâncias específicas, mas sem afetar as demais.
Variáveis mutáveis compartilhadas (armadilha)
Um erro comum é usar listas ou dicionários como atributos de classe, pois eles são mutáveis e compartilhados. Se uma instância modificar esse atributo, todas as outras instâncias serão afetadas.
class Turma:
alunos = [] # lista mutável compartilhada
def __init__(self, nome):
self.nome = nome
def adicionar_aluno(self, aluno):
self.alunos.append(aluno) # modifica a lista da classe!
t1 = Turma("A")
t2 = Turma("B")
t1.adicionar_aluno("Carlos")
print(t2.alunos) # ['Carlos'] - efeito colateral!Para evitar isso, o atributo deve ser definido como de instância, geralmente no __init__:
class Turma:
def __init__(self, nome):
self.nome = nome
self.alunos = [] # agora cada turma tem sua própria lista
def adicionar_aluno(self, aluno):
self.alunos.append(aluno)
t1 = Turma("A")
t2 = Turma("B")
t1.adicionar_aluno("Carlos")
print(t2.alunos) # [] - corretoEssa armadilha é uma das causas mais frequentes de bugs em programas orientados a objetos em Python.
Exemplos
Vamos ver um exemplo completo que combina atributos de classe e instância, além de demonstrar o comportamento de mutáveis.
class ContaBancaria:
taxa_juros = 0.02 # atributo de classe
total_contas = 0
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
ContaBancaria.total_contas += 1
def depositar(self, valor):
self.saldo += valor
def aplicar_juros(self):
self.saldo += self.saldo * ContaBancaria.taxa_juros
@classmethod
def alterar_taxa(cls, nova_taxa):
cls.taxa_juros = nova_taxa
# Uso
c1 = ContaBancaria("Alice", 1000)
c2 = ContaBancaria("Bob", 500)
print(ContaBancaria.total_contas) # 2
print(c1.taxa_juros) # 0.02
c1.aplicar_juros()
print(c1.saldo) # 1020.0
# Alterando taxa via classe
ContaBancaria.alterar_taxa(0.03)
print(c2.taxa_juros) # 0.03
# Tentativa de alterar via instância (cria atributo de instância)
c1.taxa_juros = 0.05
print(c1.taxa_juros) # 0.05 (instância)
print(c2.taxa_juros) # 0.03 (classe)Esse exemplo mostra como usar métodos de classe para modificar atributos de classe de forma controlada e como o sombreamento pode ocorrer.
Boas práticas
- Use atributos de classe para valores constantes ou compartilhados que não precisam ser copiados para cada instância.
- Evite atributos de classe mutáveis, a menos que você realmente queira o compartilhamento (raro).
- Para valores padrão que podem ser sobrescritos, prefira defini-los no
__init__. - Use métodos de classe (
@classmethod) para modificar atributos de classe de forma explícita.
Referências
- Documentação oficial: Variáveis de classe e de instância
- Real Python: Class vs Instance Attributes
- W3Schools: Python Classes and Objects
- Stack Overflow: Difference between class and instance attributes
- GeeksforGeeks: Class and Instance Attributes in Python
Exercícios
Crie uma classe
Carrocom um atributo de classerodasigual a 4 e um atributo de instânciacor. Instancie dois carros com cores diferentes e verifique se o atributorodasé compartilhado.✓ Resposta:class Carro: rodas = 4 def __init__(self, cor): self.cor = cor c1 = Carro("vermelho") c2 = Carro("azul") print(c1.rodas) # 4 print(c2.rodas) # 4 print(Carro.rodas) # 4Explique o que acontece se você fizer
c1.rodas = 6no código acima. Qual será o valor dec2.rodase deCarro.rodas?✓ Resposta: Ao fazerc1.rodas = 6, você cria um novo atributo de instânciarodasemc1, que sombreia o atributo de classe.c2.rodasainda acessa o atributo de classe (4) eCarro.rodascontinua sendo 4. Portanto,c2.rodas= 4 eCarro.rodas= 4.Crie uma classe
Configuracaocom um atributo de classeparametrosque é um dicionário vazio. Instancie dois objetos e, em um deles, adicione uma chave ao dicionário. O que acontece com o outro objeto? Como corrigir?✓ Resposta: Como dicionários são mutáveis, adicionar uma chave em um objeto modifica o dicionário compartilhado da classe, afetando todos os objetos. Para corrigir, definaself.parametros = {}no__init__, criando um dicionário próprio para cada instância.Implemente um contador de instâncias usando um atributo de classe. A classe
Animaldeve incrementar um contador a cada nova instância e ter um método de classe que retorne o total de animais criados.✓ Resposta:class Animal: total = 0 def __init__(self, nome): self.nome = nome Animal.total += 1 @classmethod def total_animais(cls): return cls.total a1 = Animal("Cachorro") a2 = Animal("Gato") print(Animal.total_animais()) # 2Qual é a diferença entre
self.atributoeClasse.atributodentro de um método de instância? Dê um exemplo onde o uso deselfpode levar a um sombreamento indesejado.✓ Resposta:self.atributorefere-se primeiro ao atributo de instância; se não existir, ao de classe.Classe.atributosempre acessa o atributo de classe. Sombreamento indesejado ocorre quando você atribui aself.atributopensando em modificar o atributo de classe, mas na verdade cria um de instância. Exemplo:class Exemplo: valor = 10 def alterar(self): self.valor = 20 # cria atributo de instância, não altera o de classe obj = Exemplo() obj.alterar() print(Exemplo.valor) # 10 print(obj.valor) # 20