
Каждое ваше
@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 построена на них ☝️
#анатомия_питона
Комментарии
0Комментариев пока нет.
Войдите, чтобы участвовать в обсуждении.