Atributos de classe e instância
Esta aula explica a diferença entre atributos de classe e de instância em Python, abordando como defini-los, acessá-los e as armadilhas com variáveis mutáveis compartilhadas. Inclui exemplos práticos e exercícios para fixação.
Em Python, atributos são variáveis associadas a objetos. No contexto de programação orientada a objetos, existem dois tipos principais: atributos de classe e atributos de instância. Compreender a diferença entre eles é fundamental para escrever código correto e eficiente, especialmente quando lidamos com dados compartilhados ou específicos de cada objeto.
Atributos de classe pertencem à classe em si, sendo compartilhados por todas as instâncias. Já os atributos de instância são exclusivos de cada objeto. Nesta aula, veremos como definir cada um, como acessá-los e uma armadilha comum envolvendo variáveis mutáveis.
Diferença
A principal diferença está no escopo e no momento de criação. Atributos de classe são definidos diretamente no corpo da classe, fora de qualquer método. Eles são criados quando a classe é definida e existem uma única vez, independentemente do número de instâncias. Atributos de instância, por outro lado, são normalmente definidos dentro do método __init__ usando self, e são criados a cada nova instância.
Para acessar um atributo de classe, podemos usar o nome da classe (ex.: MinhaClasse.atributo) ou uma instância (ex.: obj.atributo). No entanto, ao modificar um atributo via self, Python cria um atributo de instância que sombreia o de classe. Já os atributos de instância só podem ser acessados através de uma instância.
Atributos de classe
Atributos de classe são úteis para armazenar dados que são comuns a todas as instâncias, como constantes, contadores ou configurações padrão. Por exemplo, uma classe Cachorro pode ter um atributo de classe especie = 'Canis familiaris' que é o mesmo para todos os cães.
Exemplo:
class Cachorro:
especie = 'Canis familiaris' # atributo de classe
def __init__(self, nome):
self.nome = nome # atributo de instância
c1 = Cachorro('Rex')
c2 = Cachorro('Bolt')
print(c1.especie) # 'Canis familiaris'
print(c2.especie) # 'Canis familiaris'
print(Cachorro.especie) # 'Canis familiaris'Note que o atributo especie é o mesmo objeto para todas as instâncias. Se modificarmos Cachorro.especie, a mudança reflete em todas as instâncias. Porém, se tentarmos modificar via c1.especie = 'Outra', criamos um novo atributo de instância em c1, sem afetar a classe ou outras instâncias.
Variáveis mutáveis compartilhadas (armadilha)
Uma armadilha comum é usar atributos de classe para valores mutáveis, como listas ou dicionários. Como o atributo é compartilhado, qualquer modificação feita por uma instância afeta todas as outras. Isso geralmente não é o desejado.
Exemplo problemático:
class Turma:
alunos = [] # atributo de classe mutável
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('João')
print(t1.alunos) # ['João']
print(t2.alunos) # ['João'] - efeito colateral!
print(Turma.alunos) # ['João']Para evitar isso, o atributo deve ser de instância, definido dentro de __init__:
class Turma:
def __init__(self, nome):
self.nome = nome
self.alunos = [] # 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('João')
print(t1.alunos) # ['João']
print(t2.alunos) # []Exemplos
Vamos ver mais exemplos práticos para consolidar os conceitos.
Exemplo 1: Contador de instâncias usando atributo de classe.
class Pessoa:
contador = 0
def __init__(self, nome):
self.nome = nome
Pessoa.contador += 1 # incrementa o contador da classe
p1 = Pessoa('Ana')
p2 = Pessoa('Carlos')
print(Pessoa.contador) # 2Exemplo 2: Constantes compartilhadas.
class Circulo:
PI = 3.14159
def __init__(self, raio):
self.raio = raio
def area(self):
return Circulo.PI * self.raio ** 2
c = Circulo(5)
print(c.area()) # 78.53975Exemplo 3: Cuidado com mutáveis – lista de tarefas compartilhada indevidamente.
class ListaTarefas:
tarefas = [] # errado!
def __init__(self, nome):
self.nome = nome
def adicionar(self, tarefa):
self.tarefas.append(tarefa)
l1 = ListaTarefas('Casa')
l2 = ListaTarefas('Trabalho')
l1.adicionar('Limpar')
print(l2.tarefas) # ['Limpar'] - indesejadoCorreção:
class ListaTarefas:
def __init__(self, nome):
self.nome = nome
self.tarefas = []
def adicionar(self, tarefa):
self.tarefas.append(tarefa)Boas práticas
Use atributos de classe para dados que são verdadeiramente compartilhados e imutáveis (números, strings, tuplas) ou para implementar comportamentos como contadores. Evite atributos de classe mutáveis, pois podem causar bugs sutis. Prefira sempre inicializar atributos mutáveis em __init__ como atributos de instância.
Referências
- Documentação oficial: Classes e variáveis de instância
- Real Python: Classes em Python
- W3Schools: Python Classes
- Stack Overflow: Diferença entre atributos de classe e instância
- GeeksforGeeks: Class e Instance Attributes
Exercícios
Crie uma classe
Carrocom um atributo de classerodas = 4e um atributo de instânciacor. Instancie dois carros com cores diferentes e imprima o número de rodas para cada um.✓ 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) # 4Na classe
Carrodo exercício 1, tente modificar o atributorodasde uma instância para 3. Verifique se o atributo de classe foi alterado. Explique o resultado.✓ Resposta:c1.rodas = 3 print(c1.rodas) # 3 print(c2.rodas) # 4 (atributo de classe permanece) print(Carro.rodas) # 4 # Isso ocorre porque a atribuição cria um novo atributo de instância, que sombreia o de classe para c1.Crie uma classe
ContaBancariacom um atributo de classetaxa_juros(float) e um atributo de instânciasaldo. Adicione um métodoaplicar_jurosque aumenta o saldo com base na taxa de juros da classe. Teste com duas contas.✓ Resposta:class ContaBancaria: taxa_juros = 0.05 def __init__(self, saldo): self.saldo = saldo def aplicar_juros(self): self.saldo += self.saldo * ContaBancaria.taxa_juros c1 = ContaBancaria(1000) c2 = ContaBancaria(500) c1.aplicar_juros() c2.aplicar_juros() print(c1.saldo) # 1050.0 print(c2.saldo) # 525.0Explique o que acontece no código abaixo e corrija-o para que cada
Pedidotenha sua própria lista de itens.class Pedido: itens = [] def __init__(self, id): self.id = id def adicionar_item(self, item): self.itens.append(item) p1 = Pedido(1) p2 = Pedido(2) p1.adicionar_item('camisa') p2.adicionar_item('calça') print(p1.itens) print(p2.itens)✓ Resposta:O código imprime['camisa', 'calça']para ambos, poisitensé um atributo de classe compartilhado. A correção é moveritenspara o__init__:class Pedido: def __init__(self, id): self.id = id self.itens = [] def adicionar_item(self, item): self.itens.append(item)Crie uma classe
Configque armazene configurações globais como atributos de classe (ex.:versao = '1.0',debug = False). Adicione um método de classemostrar_configque imprime todas as configurações.✓ Resposta:class Config: versao = '1.0' debug = False @classmethod def mostrar_config(cls): print(f'Versão: {cls.versao}, Debug: {cls.debug}') Config.mostrar_config() # Versão: 1.0, Debug: False