Как дескриптор узнаёт имя атрибута, под которым он объявлен в классе?

Как дескриптор узнаёт имя атрибута, под которым он объявлен в классе?

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

Краткий ответ

При создании класса Python вызывает у каждого дескриптора метод __set_name__ и передаёт ему владельца класса и имя атрибута. Благодаря этому дескриптор может сам вычислить имя внутреннего хранилища или зарегистрировать метаданные без дублирования имени в его конструкторе.

Исторический контекст

До появления __set_name__ дескриптору обычно приходилось получать имя через дополнительный аргумент, метакласс или ручную настройку после объявления класса. Это создавало риск рассинхронизации: имя атрибута в классе могло отличаться от имени, переданного дескриптору.

Метод __set_name__ был добавлен в Python 3.6 как часть улучшений создания классов. Он предоставляет объектам, помещённым в пространство имён класса, уведомление о завершении подготовки этого пространства имён.

Постановка проблемы

Дескриптор часто хранит значение экземпляра не в себе, а в атрибуте владельца объекта. Для этого ему нужно знать имя поля, например преобразовать email во внутреннее имя _email.

Если имя задано вручную в нескольких местах, переименование атрибута может привести к ошибкам: дескриптор продолжит читать старое хранилище, будет конфликтовать с другим полем или случайно перезапишет данные.

Подробное решение

После выполнения тела класса Python создаёт объект класса и вызывает __set_name__(owner, name) для дескрипторов, находящихся в пространстве имён этого класса. Параметр owner — класс-владелец, а name — имя, под которым дескриптор размещён.

class Field: def __set_name__(self, owner, name): self.storage = '_' + name def __get__(self, obj, owner=None): if obj is None: return self return getattr(obj, self.storage, None) def __set__(self, obj, value): setattr(obj, self.storage, value) class User: email = Field() user = User() user.email = 'a@example.com' print(user.email) # a@example.com

При создании User дескриптор получает имя email и запоминает _email. При записи вызывается __set__, а при чтении — __get__; __set_name__ не участвует в каждом обращении к атрибуту, а выполняется на этапе подготовки класса.

Важно, что автоматический вызов происходит для объектов, присутствующих в пространстве имён класса во время его создания. Если дескриптор добавить в уже созданный класс обычным присваиванием, Python автоматически не вызовет его __set_name__; такую инициализацию нужно организовать отдельно.

Унаследованный дескриптор обычно не получает новый вызов __set_name__ при создании подкласса, потому что он не находится в пространстве имён самого подкласса. Кроме того, один экземпляр дескриптора не следует без специальной логики использовать в нескольких классах: повторный вызов может перезаписать сохранённые имя и владельца.

Ситуация из практики

В библиотеке моделей нужно реализовать несколько полей, каждое из которых проверяет значение и хранит его в экземпляре. Рассматривались три варианта: передавать имя в конструктор каждого поля, вычислять имя при каждом обращении через перебор класса или использовать __set_name__.

Передача имени в конструктор проста, но дублирует объявление и легко ломается при переименовании. Поиск имени при каждом обращении не требует настройки, однако усложняет код и добавляет лишнюю работу к операциям чтения и записи.

Выбран __set_name__: поле получает имя один раз при создании класса, а затем напрямую использует подготовленное внутреннее имя. Это уменьшает дублирование, сохраняет понятный интерфейс объявления модели и не добавляет поиска во время работы приложения.

Что кандидаты часто упускают

  1. Вызывается ли __set_name__ при каждом обращении к атрибуту?

Нет. Это метод этапа создания класса, а не часть обычного протокола чтения или записи атрибута. После сохранения метаданных дескриптор использует их в __get__, __set__ или __delete__.

  1. Сработает ли __set_name__, если дескриптор добавить в уже созданный класс?

Нет, автоматического вызова не будет. Механизм обрабатывает объекты, находившиеся в пространстве имён во время создания класса; при динамическом добавлении разработчик должен сам вызвать __set_name__ с нужным классом и именем либо применить другой механизм регистрации.

  1. Что произойдёт, если один экземпляр дескриптора присвоить двум классам?

__set_name__ может быть вызван для каждого объявления, поэтому поля owner и name, сохранённые внутри одного дескриптора, будут перезаписаны последним вызовом. Без специальной структуры данных такой дескриптор начнёт работать некорректно для первого класса; безопаснее создавать отдельный экземпляр для каждого объявления или хранить метаданные раздельно для каждой пары «класс — имя».