Зачем использовать descriptors и какую пользу они дают
Что проверяют
Понимаете ли вы, как Python на самом деле ищет атрибут, и видите ли дескрипторы там, где пользуетесь ими каждый день. Следом обычно просят объяснить разницу между data и non-data дескрипторами или написать валидирующее поле.
Как отвечать
Дескриптор это объект, который лежит атрибутом класса и определяет хотя бы один из методов __get__(self, instance, owner), __set__(self, instance, value), __delete__(self, instance). Метод __set_name__(self, owner, name) вызывается при создании класса и сообщает дескриптору имя атрибута, поэтому передавать имя вручную не нужно.
Ключевое различие в приоритете при поиске через object.__getattribute__:
- data-дескриптор (есть
__set__или__delete__): важнее, чем__dict__экземпляра. Так работаетproperty; - non-data дескриптор (только
__get__): уступает__dict__экземпляра. Так обычные функции превращаются в связанные методы, и так же устроенfunctools.cached_property: после первого вызова он кладёт значение в__dict__и больше не вызывается.
Дескрипторы уже вокруг вас: property, classmethod, staticmethod, методы, __slots__, поля моделей Django, атрибуты SQLAlchemy.
Польза собственных дескрипторов в повторном использовании логики атрибута. Один класс PositiveInt проверяет тип и диапазон для десятка полей в разных классах, тогда как с property пришлось бы копировать геттер и сеттер для каждого. Типичные применения: валидация, приведение типов, ленивые значения, логирование доступа, типизированные поля конфигурации.
Корректный __get__ обрабатывает доступ через класс: если instance is None, возвращают сам дескриптор, иначе обращение MyClass.number падает с ошибкой.
Где спотыкаются
Хранят значение в самом дескрипторе, в self.value. Дескриптор один на класс, поэтому все экземпляры начинают делить одно значение. Хранить нужно в instance.__dict__ под своим именем или в WeakKeyDictionary.
Создают дескриптор как атрибут экземпляра, через self.x = Validator() внутри __init__. Протокол работает только для атрибутов класса.
Используют cached_property в классе с __slots__ без __dict__ и получают ошибку: кешу некуда писать.