Chaque @property, classmethod, staticmethod et même l'appel de méthodes ordinaires fonctionnent sous le capot avec le même mécanisme : les descripteurs.

Un descripteur est toute classe qui implémente au moins une des méthodes du protocole : __get__, __set__ ou __delete__. Dès que vous liez une instance d'une telle classe à un attribut d'une autre classe, Python intercepte le contrôle d'accès.

1️⃣ Hiérarchie des priorités
Quand vous écrivez obj.attr, Python ne se contente pas de fouiller dans le dictionnaire. Il suit une chaîne stricte :
▪️ Data Descriptor (définit __set__ ou __delete__). Il a la priorité absolue. Si le nom de l'attribut est intercepté par un data-descripteur, Python ignore instance.__dict__. C'est pourquoi vous ne pouvez pas écraser un @property par une simple affectation (sauf si un setter est défini).
▪️ Dictionnaire de l'instance (__dict__). Données normales de l'objet.
▪️ Non-Data Descriptor (définit seulement __get__). Les méthodes de classe ordinaires en sont. Elles sont au bas de la hiérarchie des priorités. Si vous placez manuellement une fonction avec le nom d'une méthode dans __dict__ de l'objet, elle remplacera la méthode originale.

2️⃣ Anti-patron d'état partagé
Un descripteur est initialisé au niveau de la classe, pas de l'instance. Il est unique pour tous. Si vous stockez des données à l'intérieur de l'instance du descripteur, vous écraserez cette valeur pour tous les objets de votre classe. Si Vasya change son âge, Petya vieillira soudainement avec lui, car les deux instances accèdent à la même cellule mémoire du descripteur.

Comment écrire correctement ?
L'état doit être stocké strictement dans le dictionnaire de l'instance elle-même. Pour éviter les bricolages, Python 3.6+ a introduit la méthode magique __set_name__. Elle permet au descripteur de connaître le nom sous lequel il a été créé dans la classe, afin de stocker légalement les données dans le __dict__ de l'objet spécifique.

class Validator:
def __set_name__(self, owner, name):
# Mémoriser le nom de l'attribut pour éviter le codage en dur
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):
# ✅ Écrire les données uniquement dans le dictionnaire de l'INSTANCE SPÉCIFIQUE
if not isinstance(value, int):
raise TypeError("Seuls les entiers sont autorisés")
setattr(instance, self.private_name, value)

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


Les descripteurs sont un outil extrêmement puissant pour encapsuler la logique. Toute la magie des frameworks comme Django ou SQLAlchemy repose sur eux ☝️

#anatomie_de_python