Atributos de instância e de classe
1. Introdução aos Atributos em Python
1.1. Definição de atributos e sua importância em Orientação a Objetos
Atributos são variáveis associadas a uma classe ou a uma instância de classe. Eles representam o estado de um objeto e são fundamentais na programação orientada a objetos, pois permitem encapsular dados e comportamentos dentro de estruturas coesas.
1.2. Diferença fundamental entre atributos de instância e de classe
A diferença essencial está no escopo e no ciclo de vida:
- Atributos de instância: pertencem a cada objeto individualmente. Cada instância tem sua própria cópia.
- Atributos de classe: pertencem à classe em si. São compartilhados por todas as instâncias.
1.3. Como o Python gerencia o escopo de atributos
Python mantém namespaces separados: o da classe (acessível via Classe.atributo) e o da instância (acessível via self.atributo). Quando você acessa um atributo em uma instância, Python primeiro procura no namespace da instância e, se não encontrar, busca no namespace da classe.
class Exemplo:
atributo_classe = "compartilhado"
def __init__(self, valor):
self.atributo_instancia = valor
obj1 = Exemplo("A")
obj2 = Exemplo("B")
print(obj1.atributo_classe) # "compartilhado"
print(obj1.atributo_instancia) # "A"
print(obj2.atributo_instancia) # "B"
2. Atributos de Instância
2.1. Declaração e inicialização dentro de __init__
Atributos de instância são tipicamente definidos no método __init__, que é o construtor da classe:
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
p = Pessoa("Alice", 30)
print(p.nome) # Alice
print(p.idade) # 30
2.2. Acesso e modificação dinâmica
Atributos de instância podem ser acessados e modificados dinamicamente:
p.nome = "Bob"
p.nova_propriedade = "valor" # Adiciona atributo dinamicamente
print(p.nova_propriedade) # "valor"
2.3. Atributos privados (convenção _ e name mangling com __)
Python não possui atributos privados de verdade, mas usa convenções:
class Conta:
def __init__(self, saldo):
self._saldo = saldo # Convenção: "protegido"
self.__senha = "1234" # Name mangling: _Conta__senha
c = Conta(1000)
print(c._saldo) # 1000 (acessível, mas não recomendado)
# print(c.__senha) # AttributeError
print(c._Conta__senha) # "1234" (name mangling)
3. Atributos de Classe
3.1. Declaração direta no corpo da classe
Atributos de classe são declarados diretamente no corpo da classe, fora de qualquer método:
class Animal:
especie = "mamífero" # Atributo de classe
reino = "animalia" # Atributo de classe
def __init__(self, nome):
self.nome = nome # Atributo de instância
print(Animal.especie) # "mamífero"
3.2. Comportamento compartilhado entre todas as instâncias
Todas as instâncias compartilham o mesmo atributo de classe:
gato = Animal("Felix")
cachorro = Animal("Rex")
print(gato.especie) # "mamífero"
print(cachorro.especie) # "mamífero"
Animal.especie = "ave"
print(gato.especie) # "ave" - mudou para todas
3.3. Modificação de atributos de classe via classe vs. via instância
Modificar via instância cria um atributo de instância que sombreia o de classe:
class Config:
debug = False
config1 = Config()
config2 = Config()
config1.debug = True # Cria atributo de instância
print(config1.debug) # True
print(config2.debug) # False (ainda usa o da classe)
print(Config.debug) # False (não foi alterado)
4. Resolução de Atributos e Ordem de Busca (MRO)
4.1. Como o Python busca atributos: instância → classe → superclasse
A ordem de busca (Method Resolution Order - MRO) é: instância → classe → superclasse(s):
class A:
x = "classe A"
class B(A):
x = "classe B"
class C(B):
pass
obj = C()
obj.x = "instância"
print(obj.x) # "instância"
del obj.x
print(obj.x) # "classe B" (busca na classe C, depois B)
del B.x
print(obj.x) # "classe A" (busca na superclasse)
4.2. O papel do dicionário __dict__ da instância e da classe
Cada objeto e classe tem um dicionário __dict__ que armazena seus atributos:
class Demo:
class_attr = 42
def __init__(self):
self.instance_attr = 10
d = Demo()
print(d.__dict__) # {'instance_attr': 10}
print(Demo.__dict__) # {'class_attr': 42, ...}
4.3. Exemplo prático de sombreamento (shadowing)
class Empresa:
taxa_juros = 0.05
empresa1 = Empresa()
empresa2 = Empresa()
empresa1.taxa_juros = 0.07 # Sombramento
print(empresa1.taxa_juros) # 0.07 (instância)
print(empresa2.taxa_juros) # 0.05 (classe)
print(Empresa.taxa_juros) # 0.05 (classe)
5. Mutabilidade e Cuidados com Atributos de Classe
5.1. Atributos de classe imutáveis vs. mutáveis
Atributos imutáveis (int, str, tuple) são seguros; mutáveis (list, dict, set) requerem cuidado:
class Turma:
alunos = [] # Mutável - compartilhado!
nome_escola = "Escola XYZ" # Imutável - seguro
t1 = Turma()
t2 = Turma()
t1.alunos.append("João")
print(t2.alunos) # ["João"] - compartilhou!
5.2. O perigo de modificar objetos mutáveis compartilhados
O exemplo acima mostra que modificar uma lista compartilhada afeta todas as instâncias. Isso é um bug comum.
5.3. Boas práticas: quando usar atributos de classe e quando evitar
- Use atributos de classe para: constantes, configurações globais, contadores compartilhados
- Evite atributos de classe para: listas, dicionários ou objetos mutáveis que podem ser modificados por instâncias
- Prefira atributos de instância para dados que variam entre objetos
6. Métodos de Classe e @classmethod para Manipular Atributos de Classe
6.1. Acessando e alterando atributos de classe dentro de métodos de classe
class Contador:
total_instancias = 0
def __init__(self):
Contador.total_instancias += 1
@classmethod
def resetar_contador(cls):
cls.total_instancias = 0
@classmethod
def exibir_total(cls):
return f"Total de instâncias: {cls.total_instancias}"
c1 = Contador()
c2 = Contador()
print(Contador.exibir_total()) # Total de instâncias: 2
Contador.resetar_contador()
print(Contador.exibir_total()) # Total de instâncias: 0
6.2. Exemplo: contador de instâncias usando atributo de classe
class Pedido:
proximo_id = 1
def __init__(self, descricao):
self.id = Pedido.proximo_id
self.descricao = descricao
Pedido.proximo_id += 1
@classmethod
def ultimo_id_gerado(cls):
return cls.proximo_id - 1
pedido1 = Pedido("Camiseta")
pedido2 = Pedido("Caneca")
print(pedido1.id) # 1
print(pedido2.id) # 2
print(Pedido.ultimo_id_gerado()) # 2
6.3. Diferença entre cls.atributo e self.atributo
class Exemplo:
valor = 10
def metodo_instancia(self):
print(self.valor) # Busca: instância → classe
@classmethod
def metodo_classe(cls):
print(cls.valor) # Busca diretamente na classe
e = Exemplo()
e.valor = 20
e.metodo_instancia() # 20 (instância sombreou)
e.metodo_classe() # 10 (classe)
7. Atributos Dinâmicos e __slots__
7.1. Adicionando atributos de instância fora da classe
class Dinamico:
pass
obj = Dinamico()
obj.novo_atributo = "criado dinamicamente"
print(obj.novo_atributo) # "criado dinamicamente"
7.2. Restringindo atributos com __slots__ para economia de memória
class Otimizado:
__slots__ = ['nome', 'idade']
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
o = Otimizado("Maria", 25)
# o.email = "maria@email.com" # AttributeError!
7.3. Limitações e impacto de __slots__ na herança
class Base:
__slots__ = ['x']
class Derivada(Base):
__slots__ = ['y'] # Precisa declarar slots novamente
def __init__(self):
self.x = 10
self.y = 20
d = Derivada()
# d.z = 30 # AttributeError (a menos que Derivada declare 'z' em __slots__)
8. Casos Práticos e Boas Práticas
8.1. Exemplo completo: sistema de banco com saldo mínimo como atributo de classe
class ContaBancaria:
taxa_juros = 0.02
saldo_minimo = 100.00
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self._saldo = max(saldo_inicial, ContaBancaria.saldo_minimo)
self._transacoes = []
def depositar(self, valor):
self._saldo += valor
self._transacoes.append(f"Depósito: R${valor:.2f}")
def sacar(self, valor):
if self._saldo - valor >= ContaBancaria.saldo_minimo:
self._saldo -= valor
self._transacoes.append(f"Saque: R${valor:.2f}")
else:
print("Saldo insuficiente para manter saldo mínimo")
@classmethod
def alterar_saldo_minimo(cls, novo_minimo):
if novo_minimo >= 0:
cls.saldo_minimo = novo_minimo
print(f"Novo saldo mínimo: R${novo_minimo:.2f}")
else:
print("Saldo mínimo não pode ser negativo")
@property
def saldo(self):
return self._saldo
def __str__(self):
return f"Conta de {self.titular} - Saldo: R${self._saldo:.2f}"
conta1 = ContaBancaria("Ana", 500)
conta2 = ContaBancaria("Carlos", 200)
conta1.depositar(300)
conta1.sacar(50)
print(conta1) # Conta de Ana - Saldo: R$750.00
ContaBancaria.alterar_saldo_minimo(50)
print(f"Saldo mínimo atual: R${ContaBancaria.saldo_minimo:.2f}")
8.2. Quando optar por atributo de classe vs. atributo de instância
- Atributo de classe: valores constantes, configurações globais, contadores, dados compartilhados (imutáveis)
- Atributo de instância: dados específicos de cada objeto, estados mutáveis, informações únicas
8.3. Debugging: inspecionando atributos com vars(), dir() e getattr()
class Produto:
imposto = 0.1
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
p = Produto("Notebook", 3500)
print(vars(p)) # {'nome': 'Notebook', 'preco': 3500}
print(dir(p)) # Lista todos os atributos e métodos
print(getattr(p, 'nome')) # "Notebook"
print(getattr(p, 'preco', 0)) # 3500
print(getattr(p, 'desconto', 'Não definido')) # "Não definido"
Referências
- Documentação Oficial: Classes — Tutorial completo sobre classes em Python, incluindo atributos de classe e instância
- Real Python: Instance and Class Attributes — Guia detalhado comparando atributos de classe e instância com exemplos práticos
- Python.org: Data Model — Documentação oficial sobre o modelo de dados, incluindo namespaces e resolução de atributos
- GeeksforGeeks: Class vs Instance Attributes — Tutorial com exemplos claros sobre a diferença entre atributos de classe e instância
- Programiz: Python slots — Explicação detalhada sobre
__slots__, economia de memória e limitações