ПрограммированиеSenior Python разработчик

Что такое дескрипторы в Python? Как они реализуются, для чего применяются, в чем отличие от property и каковы типичные ошибки при их использовании?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Дескриптор — это объект, реализующий любой из методов get, set и delete. Он позволяет контролировать доступ к атрибутам класса.

Стандартные методы дескриптора:

  • __get__(self, instance, owner)
  • __set__(self, instance, value)
  • __delete__(self, instance)

Дескрипторы бывают:

  • Классические (data descriptors) — реализуют set, имеют приоритет над обычными атрибутами;
  • Не-классические (non-data descriptors) — реализуют только get, уступают обычному атрибуту.

Пример использования:

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, из-за чего обычный атрибут экземпляра перекрывал дескриптор, ломая весь механизм валидации — баг проявлялся не всегда, сложен для отладки.