Módulo 11 - Metaclasses e descritores

Interceptar atributos: __getattr__ e amigos

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Diferenciar __getattr__ (só o que falta) de __getattribute__ (todo acesso).
  • Usar __setattr__ para validar ou controlar a escrita de atributos.
  • Evitar o erro clássico de recursão infinita nesses métodos.
  • Reconhecer casos legítimos como proxies e objetos dinâmicos.

__getattr__ contra __getattribute__

Os descritores da aula anterior ficam na classe e controlam um atributo específico. Estes ganchos ficam no objeto e controlam o acesso a atributos em geral. O par mais importante é __getattr__ e __getattribute__, e a diferença entre eles é sutil e decisiva. O __getattribute__ é chamado em toda leitura de atributo, sem exceção. Já o __getattr__ é o plano B: ele só é chamado quando a busca normal falha, isto é, quando o atributo não foi encontrado. Na prática, você quase sempre quer __getattr__, porque ele não atrapalha os atributos que já existem.

class Config:
    def __init__(self):
        self.tema = "claro"

    def __getattr__(self, nome):
        # So chamado quando o atributo nao existe de verdade.
        return f"[opcao {nome} nao definida]"

c = Config()
print(c.tema)   # claro  (existe: __getattr__ nem e chamado)
print(c.idioma) # [opcao idioma nao definida]  (nao existe: cai no __getattr__)

__getattr__ atua só no que falta; tema existe e passa direto, idioma cai no gancho.

O exemplo deixa a diferença palpável. Como tema existe no objeto, lê-lo não aciona __getattr__. Já idioma não existe, então a busca normal falha e o Python chama __getattr__, que devolve um valor padrão amigável em vez de levantar AttributeError. Esse padrão é a base de objetos de configuração flexíveis e de proxies que repassam chamadas para outro objeto. Se você usasse __getattribute__ aqui, teria que tratar todo e qualquer acesso, inclusive o de tema, e o risco de erro cresceria muito.

__setattr__ e a armadilha da recursão

Para o lado da escrita existe o __setattr__, chamado toda vez que você faz objeto.atributo = valor. Ele é útil para validar antes de gravar, tornar um objeto imutável ou registrar mudanças. Mas aqui mora a armadilha mais famosa desses ganchos: a recursão infinita. Dentro de __setattr__, se você escrever self.nome = valor para guardar o dado, isso chama __setattr__ de novo, que chama de novo, sem fim. A saída correta é gravar direto no dicionário do objeto, com super().__setattr__ ou self.__dict__.

class Registro:
    def __setattr__(self, nome, valor):
        if not nome.isidentifier():
            raise ValueError(f"nome de campo invalido: {nome}")
        print(f"gravando {nome} = {valor!r}")
        # Certo: grava sem chamar __setattr__ de novo.
        super().__setattr__(nome, valor)
        # ERRADO seria: self.__dict__[nome] direto tambem serve,
        # mas self.nome = valor causaria recursao infinita.

r = Registro()
r.saldo = 100   # gravando saldo = 100
print(r.saldo)  # 100

Em __setattr__, use super().__setattr__ para gravar sem recursão infinita.

Quando esses ganchos valem a pena

Como quase tudo neste módulo, esses ganchos são poderosos e pedem parcimônia. Os casos legítimos são bem definidos. Um proxy, objeto que repassa chamadas para outro, usa __getattr__ para delegar tudo que não conhece. Um objeto de configuração ou um wrapper sobre dados semiestruturados usa __getattr__ para expor chaves como atributos. Um objeto imutável usa __setattr__ para recusar mudanças depois de construído. Todos esses são padrões reconhecíveis, com uma razão clara.

O que você deve evitar é usar esses ganchos para economizar algumas linhas em código comum. Um objeto que intercepta todo acesso a atributo é difícil de depurar, confunde ferramentas de autocompletar e esconde erros de digitação, já que um atributo escrito errado pode simplesmente cair no __getattr__ em vez de estourar. Sempre que um descritor, uma property ou um atributo comum resolverem, prefira-os. Reserve __getattr__ para delegação e objetos genuinamente dinâmicos, e __getattribute__ quase nunca, pois raríssimo é o problema que só ele resolve.

Teste rápido

Qual a diferença central entre __getattr__ e __getattribute__?

Perguntas frequentes

Por que __getattr__ é preferível a __getattribute__ na maioria dos casos?
Porque __getattr__ só age quando o atributo não existe, deixando o acesso normal intacto e rápido. __getattribute__ intercepta tudo, exige tratar cada acesso e é fácil de quebrar com recursão. Para valores dinâmicos e proxies, __getattr__ dá o mesmo resultado com muito menos risco.
O que causa o RecursionError nesses métodos?
Acessar o próprio atributo pelo caminho comum dentro do gancho. Em __setattr__, self.nome = valor chama __setattr__ de novo; em __getattribute__, self.x chama __getattribute__ de novo. A solução é usar super().__setattr__, super().__getattribute__ ou self.__dict__ para acessar sem reentrar no gancho.
Descritor ou __getattr__, quando usar cada um?
Use um descritor quando quer controlar um atributo específico e talvez reaproveitar a regra entre classes. Use __getattr__ quando quer um comportamento genérico para atributos que não existem, como delegar para outro objeto. Descritor é cirúrgico; __getattr__ é abrangente.
Esses ganchos funcionam com métodos também?
Sim, porque métodos são apenas atributos que resultam em objetos chamáveis. Um proxy com __getattr__, por exemplo, pode repassar chamadas de método para o objeto interno. O acesso ao método passa pelo mesmo mecanismo de busca de atributo que os dados.
Usar __getattr__ atrapalha o autocompletar do editor?
Pode atrapalhar, sim. Como os atributos são resolvidos em tempo de execução, o editor não sabe de antemão quais existem e o autocompletar fica cego. É mais um motivo para reservar esses ganchos a casos que realmente pedem dinamismo, e não para código comum.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.