Cada @property, classmethod, staticmethod e incluso la llamada a métodos normales funciona bajo el capó con el mismo mecanismo: los descriptores.

Un descriptor es cualquier clase que implementa al menos uno de los métodos del protocolo: __get__, __set__ o __delete__. En cuanto vinculas una instancia de dicha clase a un atributo de otra clase, Python intercepta el control de acceso.

1️⃣ Jerarquía de prioridades
Cuando escribes obj.attr, Python no solo busca en el diccionario. Sigue una cadena estricta:
▪️ Data Descriptor (definidos __set__ o __delete__). Tiene prioridad absoluta. Si el nombre del atributo es interceptado por un data descriptor, Python ignora instance.__dict__. Por eso no puedes sobrescribir @property con una asignación normal (a menos que tenga setter).
▪️ Diccionario de la instancia (__dict__). Datos normales del objeto.
▪️ Non-Data Descriptor (solo definido __get__). Los métodos normales de clase son estos. Están al final de la prioridad. Si pones manualmente una función con el nombre del método en __dict__ del objeto, esta anulará el método original.

2️⃣ Anti-patrón de estado compartido
El descriptor se inicializa a nivel de clase, no de instancia. Es uno para todos. Si guardas datos dentro de la instancia del descriptor, sobrescribirás ese valor para todos los objetos de tu clase. Si Vasya cambia la edad, Petya envejecerá de repente con él, porque ambas instancias acceden a la misma celda de memoria del descriptor.

¿Cómo escribir correctamente?
El estado debe almacenarse estrictamente en el diccionario de la propia instancia. Para no escribir parches, en Python 3.6+ se introdujo el método mágico __set_name__. Permite que el descriptor sepa bajo qué nombre se creó en la clase, para guardar legalmente los datos en __dict__ del objeto específico.

class Validator:
def __set_name__(self, owner, name):
# Recordamos el nombre del atributo para no hardcodear
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):
# ✅ Escribimos datos solo en el diccionario de la INSTANCIA CONCRETA
if not isinstance(value, int):
raise TypeError("Only integers allowed")
setattr(instance, self.private_name, value)

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


Los descriptores son una herramienta potentísima para encapsular lógica. Toda la magia de frameworks como Django o SQLAlchemy está construida sobre ellos ☝️

#anatomia_de_python