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

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

Почему так происходит — разбор по шагам
- Что такое дескриптор:
- Объект, определённый как атрибут класса, который реализует специальные методы (__get__, __set__, __delete__).
- Если реализован __set__ (или __delete__), то это data-дескриптор (data descriptor). Если только __get__, то non-data дескриптор.
- Порядок поиска атрибута при обращении i.x:
- Сначала проверяются data-дескрипторы в классе.
- Затем — атрибут в словаре экземпляра (i.__dict__).
- Затем — non-data дескрипторы и остальные атрибуты класса.
- Наличие __set__ делает Desc data-дескриптором, поэтому его __get__ имеет приоритет над любыми значениями в i.__dict__.
- Что происходит при выполнении i.x = 'INST':
- Поскольку Desc реализует __set__, присвоение вызывает именно этот метод, а не записывает ключ 'x' в i.__dict__.
- __set__ в коде ставит на экземпляр атрибут x_from_set = 'WAS_SET'.
- Экземплярного x (i.__dict__['x']) после присвоения нет.
- Что возвращает i.x при чтении:
- Так как Desc — data-дескриптор, чтение вызывает его __get__, которая возвращает строку 'DESC' (для доступа через экземпляр).
Следовательно, финальный распечатанный результат — строка 'DESC' и строка 'WAS_SET'.
Демонстрация: оригинальный и изменённый варианты
Оригинальный (data-дескриптор):

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

Объяснение: без __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 — для вычисляемых свойств без вмешательства в словарь экземпляра.