Cada @property, classmethod, staticmethod e até mesmo a chamada de métodos comuns funcionam sob o capô com o mesmo mecanismo. Nos descritores.

Um descritor é qualquer classe que implementa pelo menos um dos métodos do protocolo: __get__, __set__ ou __delete__. Assim que você vincula uma instância dessa classe a um atributo de outra classe, o Python intercepta o controle de acesso.

1️⃣ Hierarquia de prioridades
Quando você escreve obj.attr, o Python não simplesmente acessa o dicionário. Ele segue uma cadeia estrita:
▪️ Data Descriptor (definidos __set__ ou __delete__). Tem prioridade absoluta. Se o nome do atributo é interceptado por um Data Descriptor, o Python ignora instance.__dict__. É por isso que você não pode sobrescrever um @property com uma atribuição comum (a menos que haja um setter).
▪️ Dicionário da instância (__dict__). Dados comuns do objeto.
▪️ Non-Data Descriptor (definido apenas __get__). Métodos comuns de classe são exatamente isso. Eles estão no fundo das prioridades. Se você colocar manualmente uma função com o nome do método no __dict__ do objeto, ela substituirá o método original.

2️⃣ Anti-padrão de estado compartilhado
O descritor é inicializado no nível da classe, não da instância. Ele é único para todos. Se você armazenar dados dentro da instância do descritor, sobrescreverá esse valor para todos os objetos da sua classe. Se o Vasya mudar a idade, o Petya envelhecerá junto com ele, porque ambas as instâncias acessam a mesma célula de memória do descritor.

Como escrever corretamente?
O estado deve ser armazenado estritamente no dicionário da própria instância. Para não escrever gambiarras, no Python 3.6+ foi introduzido o método mágico __set_name__. Ele permite que o descritor saiba sob qual nome foi criado na classe, para armazenar dados legalmente no __dict__ do objeto específico.

class Validator:
def __set_name__(self, owner, name):
# Lembra o nome do atributo para não hardcodar
self.private_name = f"_{name}"

def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name)

def __set__(self, instance, value):
# ✅ Escreve dados apenas no dicionário da INSTÂNCIA ESPECÍFICA
if not isinstance(value, int):
raise TypeError("Only integers allowed")
setattr(instance, self.private_name, value)

class User:
age = Validator() # age_validator.__set_name__ receberá name='age'


Descritores são uma ferramenta poderosa para encapsular lógica. Toda a magia de frameworks como Django ou SQLAlchemy é construída sobre eles ☝️

#anatomia_do_python