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.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Descritores: como @property funciona por dentro.
Os objetivos desta aula. 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.
Veja o essencial, parte por parte.
O protocolo dos descritores. Um descritor é um objeto com __get__, __set__ ou __delete__, colocado num atributo de classe.
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.
property também é um descritor. Agora a peça se encaixa.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
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 leituraUm 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 negativoUma 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.