Каждое ваше @property, classmethod, staticmethod и даже вызов обычных методов под капотом работает на одном и том же механизме. На дескрипторах.

Дескриптор — это любой класс, который реализует хотя бы один из методов протокола: __get__, __set__ или __delete__. Как только вы привязываете экземпляр такого класса к атрибуту другого класса, Python перехватывает управление доступом.

1️⃣ Иерархия приоритетов
Когда вы пишете obj.attr, питон не просто лезет в словарь. Он идет по строгой цепочке:
▪️ Data Descriptor (определены __set__ или __delete__). У него абсолютный приоритет. Если имя атрибута перехватывается Data-дескриптором, питон плюнет на instance.__dict__. Именно поэтому вы не можете перезаписать @property обычным присваиванием (если нет сеттера).
▪️ Словарь экземпляра (__dict__). Обычные данные объекта.
▪️ Non-Data Descriptor (определен только __get__). Обычные методы класса — это они и есть. Они лежат на дне приоритетов. Если вы руками положите в __dict__ объекта функцию с именем метода, она перекроет оригинальный метод.

2️⃣ Shared State Anti-pattern
Дескриптор инициализируется на уровне класса, а не экземпляра. Он один на всех. Если вы сохраните данные внутри инстанса дескриптора, вы перезапишете это значение для всех объектов вашего класса. Если Вася изменит возраст, Петя внезапно постареет вместе с ним, потому что оба инстанса стучатся в одну и ту же ячейку памяти дескриптора.

Как писать правильно?
Хранить состояние нужно строго в словаре самого инстанса. Чтобы не писать костыли, в Python 3.6+ завезли магический метод __set_name__. Он позволяет дескриптору узнать, под каким именем его создали в классе, чтобы легально сохранить данные в __dict__ конкретного объекта.

class Validator:
def __set_name__(self, owner, name):
# Запоминаем имя атрибута, чтобы не хардкодить
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):
# ✅ Пишем данные только в словарь КОНКРЕТНОГО инстанса
if not isinstance(value, int):
raise TypeError("Only integers allowed")
setattr(instance, self.private_name, value)

class User:
age = Validator() # age_validator.__set_name__ получит name='age'


Дескрипторы — это мощнейший инструмент инкапсуляции логики. Вся магия фреймворков вроде Django или SQLAlchemy построена на них ☝️

#анатомия_питона