Módulo 11 - Metaclasses e descritores

Descritores: como @property funciona por dentro

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

O que você vai aprender

  • Definir descritor como um objeto com __get__, __set__ ou __delete__.
  • Escrever um descritor de validação reutilizável entre classes.
  • Usar __set_name__ para o descritor saber o nome do atributo.
  • Entender que @property é um descritor pronto embutido na linguagem.

O protocolo dos descritores

Um descritor é qualquer objeto que define um ou mais dos métodos __get__, __set__ e __delete__, e que é atribuído a um atributo de uma classe. A partir daí, sempre que alguém lê, escreve ou apaga aquele atributo numa instância, o Python não mexe direto no dicionário do objeto: ele chama o método correspondente do descritor. É esse desvio que dá a você o controle. O protocolo é discreto, mas está por toda parte: métodos, classmethod, staticmethod e property são todos implementados como descritores.

class SoLeitura:
    def __init__(self, valor):
        self.valor = valor

    def __get__(self, obj, tipo=None):
        return self.valor

    def __set__(self, obj, novo):
        raise AttributeError("este atributo e somente leitura")

class Config:
    versao = SoLeitura("1.0")

c = Config()
print(c.versao)   # 1.0  (passa por __get__)
try:
    c.versao = "2.0"
except AttributeError as e:
    print("bloqueado:", e)  # bloqueado: este atributo e somente leitura

Um descritor simples: __get__ devolve o valor, __set__ recusa a escrita.

O exemplo mostra o mecanismo no osso. A leitura c.versao passa por __get__, e a tentativa de escrita c.versao = '2.0' passa por __set__, que recusa. Note os argumentos: __get__ recebe obj, a instância que está sendo acessada, e __set__ recebe também o novo valor. Com isso, um único objeto descritor consegue orquestrar o comportamento do atributo em todas as instâncias da classe, sem repetir código.

Um descritor de validação reutilizável

O poder real dos descritores aparece quando você precisa da mesma regra de validação em vários lugares. Imagine que preço, salário e altura precisam, todos, ser números não negativos. Sem descritores, você repetiria a checagem em cada property. Com um descritor, escreve a regra uma vez e a aplica onde quiser. O método __set_name__ entra aqui: ele informa ao descritor o nome do atributo, para que ele guarde o valor no lugar certo do dicionário da instância.

class NaoNegativo:
    def __set_name__(self, dono, nome):
        self.nome = "_" + nome  # nome real onde o valor fica guardado

    def __get__(self, obj, tipo=None):
        if obj is None:
            return self
        return getattr(obj, self.nome)

    def __set__(self, obj, valor):
        if valor < 0:
            raise ValueError(f"{self.nome[1:]} nao pode ser negativo")
        setattr(obj, self.nome, valor)

class Produto:
    preco = NaoNegativo()
    estoque = NaoNegativo()

    def __init__(self, preco, estoque):
        self.preco = preco
        self.estoque = estoque

p = Produto(10, 5)
print(p.preco, p.estoque)  # 10 5
try:
    p.preco = -1
except ValueError as e:
    print("erro:", e)  # erro: preco nao pode ser negativo

Uma regra de validação escrita uma vez, aplicada a preco e estoque com __set_name__.

Veja a economia: a regra de não negativo mora num único lugar, a classe NaoNegativo, e é reaproveitada por preco e por estoque. O __set_name__ é chamado pelo Python assim que a classe Produto é montada, uma vez para preco e outra para estoque, e cada descritor descobre sob qual nome foi definido. Assim os valores de preco e estoque não colidem: cada um fica guardado em sua chave própria (_preco e _estoque) no dicionário da instância. Esse padrão é exatamente o que bibliotecas de validação e ORMs usam por baixo.

property também é um descritor

Agora a peça se encaixa. Quando você escreve @property, o Python cria para você um objeto descritor que já tem __get__, __set__ e __delete__ prontos, ligados às funções que você decorou. Ou seja, property é um descritor de conveniência: cobre o caso mais comum, um atributo calculado ou validado dentro de uma única classe, sem você escrever o protocolo na mão. Saber disso muda como você lê o código: property deixa de ser mágica e vira um descritor com nome amigável.

Quando usar @property

  • A regra vale para uma única classe
  • É um atributo calculado ou com validação simples
  • Você quer o caminho mais curto e legível
  • Sem intenção de reaproveitar a lógica

Quando escrever um descritor próprio

  • A mesma regra se repete em várias classes ou atributos
  • A validação é rica e merece um objeto próprio
  • Você quer encapsular a lógica e testá-la isolada
  • É a base de uma biblioteca ou framework

A conclusão prática é tranquilizadora. Na maioria dos casos, @property continua sendo a resposta certa, e agora você entende o que ela faz por dentro. Só quando a mesma regra se espalha por muitos atributos e classes é que vale escrever um descritor próprio, para não repetir código. Descritores são a fundação sobre a qual property, os métodos e boa parte do modelo de objetos são construídos, e conhecê-los te dá raio-x de bibliotecas inteiras.

Teste rápido

Qual afirmação sobre @property e descritores está correta?

Perguntas frequentes

Qual a diferença entre descritor de dados e de não-dados?
Um descritor de dados define __set__ ou __delete__; um de não-dados define apenas __get__. A distinção importa na ordem de resolução: descritores de dados têm prioridade sobre o dicionário da instância, enquanto os de não-dados podem ser sobrescritos por um atributo de mesmo nome na instância.
Por que o descritor precisa ficar num atributo de classe, não de instância?
O protocolo de descritores só é acionado quando o objeto está em um atributo da classe. Se você colocar o descritor no dicionário da instância, o Python o trata como um valor comum e não chama __get__ nem __set__. Por isso descritores são sempre definidos no corpo da classe.
Para que serve o teste if obj is None em __get__?
Quando você acessa o atributo pela classe, e não por uma instância, obj chega como None. Devolver self nesse caso permite acessar o próprio descritor, comportamento esperado por ferramentas de introspecção. É uma convenção comum em descritores bem escritos.
Descritores deixam o código mais lento?
Há um custo pequeno, já que cada acesso passa por uma chamada de método, mas é desprezível na maioria dos casos. A clareza e o reaproveitamento costumam compensar de sobra. Se performance for crítica em um ponto quente, meça antes de decidir, como manda o módulo de profiling.
Quando prefiro property em vez de um descritor próprio?
Sempre que a lógica vale para uma única classe e não se repete. property é mais curta e legível. Só escreva um descritor próprio quando a mesma regra aparece em vários atributos ou classes, momento em que reaproveitar a lógica num objeto compensa a complexidade extra.

Fontes

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