Что такое property и descriptors в Python
Что проверяют
Понимаете ли вы, как в Python устроен доступ к атрибутам, и знаете ли, что property это не отдельная магия, а готовый частный случай протокола дескрипторов. Вопрос отделяет тех, кто ставил @property по образцу, от тех, кто объяснит порядок разрешения имени.
Как отвечать
Дескриптор это объект, у класса которого определён хотя бы один из методов __get__, __set__, __delete__, и который положен атрибутом класса, а не экземпляра. Если есть __set__ или __delete__, это дескриптор данных, и он имеет приоритет над __dict__ экземпляра. Если только __get__, приоритет обратный: значение в __dict__ его перекроет, на этом построен functools.cached_property.
property это дескриптор данных, собранный за вас: он перехватывает чтение и запись, позволяет добавить проверку и вычисление, сохранив синтаксис обычного поля. Дескриптор пишут руками, когда одна и та же логика нужна многим полям и классам.
class Positive:
def __set_name__(self, owner, name):
self.slot = "_" + name
def __get__(self, obj, owner=None):
if obj is None:
return self
return getattr(obj, self.slot)
def __set__(self, obj, value):
if value < 0:
raise ValueError("value must be non-negative")
setattr(obj, self.slot, value)
class Circle:
radius = Positive()На дескрипторах держатся staticmethod, classmethod и поля ORM.
Где спотыкаются
Хранят значение внутри самого дескриптора (self.value = value). Дескриптор существует в одном экземпляре на класс, поэтому значение немедленно становится общим для всех объектов. Хранить нужно в экземпляре, а имя слота получать в __set_name__.
Забывают ветку obj is None в __get__. Тогда обращение к атрибуту на самом классе падает вместо того, чтобы вернуть дескриптор, и ломаются help, автодополнение и любая интроспекция.
Называют property средством инкапсуляции. Подчёркивание перед именем это соглашение, а не запрет, и obj._radius прекрасно читается снаружи. Property даёт точку контроля, а не защиту. Отдельная ловушка: спрятать за property сетевой запрос, и обычное на вид чтение поля начинает ходить в базу.