Дескриптор и порядок поиска атрибутов в Python

Дескриптор и порядок поиска атрибутов в Python

@python_quiz

Разберем этот квиз.

Условие (кратко)

Есть дескриптор, который реализует оба метода — __get__ и __set__. Класс C объявляет атрибут x как этот дескриптор. Затем создаётся экземпляр, выполняется запись i.x = 'INST' и печатаются i.x и getattr(i, 'x_from_set', None).

Код:

python

Вывод программы:

Почему так происходит — разбор по шагам

  1. Что такое дескриптор:

- Объект, определённый как атрибут класса, который реализует специальные методы (__get__, __set__, __delete__).
- Если реализован __set__ (или __delete__), то это data-дескриптор (data descriptor). Если только __get__, то non-data дескриптор.

  1. Порядок поиска атрибута при обращении i.x:

- Сначала проверяются data-дескрипторы в классе.
- Затем — атрибут в словаре экземпляра (i.__dict__).
- Затем — non-data дескрипторы и остальные атрибуты класса.
- Наличие __set__ делает Desc data-дескриптором, поэтому его __get__ имеет приоритет над любыми значениями в i.__dict__.

  1. Что происходит при выполнении i.x = 'INST':

- Поскольку Desc реализует __set__, присвоение вызывает именно этот метод, а не записывает ключ 'x' в i.__dict__.
- __set__ в коде ставит на экземпляр атрибут x_from_set = 'WAS_SET'.
- Экземплярного x (i.__dict__['x']) после присвоения нет.

  1. Что возвращает i.x при чтении:

- Так как Desc — data-дескриптор, чтение вызывает его __get__, которая возвращает строку 'DESC' (для доступа через экземпляр).

Следовательно, финальный распечатанный результат — строка 'DESC' и строка 'WAS_SET'.

Демонстрация: оригинальный и изменённый варианты

Оригинальный (data-дескриптор):

python

Если удалить __set__ (сделать non-data дескриптор), поведение изменится:

python

Объяснение: без __set__ дескриптор не препятствует записи в словарь экземпляра, поэтому значение экземпляра затеняет дескриптор при чтении.

Анализ возможных вариантов результата

  • "DESC WAS_SET" — соответствует поведению data-дескриптора: __set__ сработал, __get__ вернул 'DESC'.
  • "INST WAS_SET" — невозможно здесь, потому что наличие __set__ предотвращает создание экземплярного атрибута x; поэтому чтение не вернёт 'INST'.
  • "DESC None" — невозможно, потому что __set__ явно создаёт атрибут x_from_set='WAS_SET', так что None не вернётся.
  • "INST None" — соответствует сценарию без __set__ (non-data дескриптор), но не для приведённого кода.

Выводы и практические советы

  • Наличие __set__ превращает объект в data-дескриптор и даёт ему приоритет при чтении/записи атрибутов экземпляра.
  • Если вы хотите, чтобы дескриптор позволял экземпляру хранить своё значение (т.е. экземплярное значение затеняло дескриптор), не реализуйте __set__.
  • Используйте data-дескрипторы для контроля записи (валидация, ведение метаданных, вычисляемые свойства с сохранением побочных эффектов), а non-data — для вычисляемых свойств без вмешательства в словарь экземпляра.

Report Page