Дескриптор — это объект, реализующий любой из методов get, set и delete. Он позволяет контролировать доступ к атрибутам класса.
Стандартные методы дескриптора:
__get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)Дескрипторы бывают:
Пример использования:
class OnlyPositive: def __init__(self): self._name = '_value' def __get__(self, instance, owner): return instance.__dict__[self._name] def __set__(self, instance, value): if value < 0: raise ValueError('Value must be >= 0') instance.__dict__[self._name] = value class Account: value = OnlyPositive() def __init__(self, value): self.value = value acc = Account(10) acc.value = -1 # ValueError!
property — лишь синтаксический сахар для создания дескрипторов на уровне класса.
Что будет, если создать дескриптор как атрибут экземпляра, а не класса?
Многие считают, что дескриптор будет работать, но это не так.
Правильный ответ:
Дескриптор работает только если определён непосредственно как атрибут класса. Если задать дескриптор как атрибут экземпляра, методы get/set не вызовутся — обычная логика обращения к атрибуту.
История
Использование property вместо полноценного дескриптора
Для валидации и других сопряжённых атрибутов разработчик использовал простой property, что приводило к частому дублированию логики и невозможности переиспользовать код между разными классами.
История
Несоблюдение иммутабельности при переиспользовании хранения
Дескриптор хранил данные во внутреннем атрибуте собственного класса (self.x), а не во внутреннем хранилище экземпляра, из-за чего атрибут становился "общим", и разные инстансы класса перезаписывали друг другу значения — "утекали" данные между объектами.
История
Перепутан data descriptor и non-data descriptor
В сложной иерархии пропущена реализация set, из-за чего обычный атрибут экземпляра перекрывал дескриптор, ломая весь механизм валидации — баг проявлялся не всегда, сложен для отладки.