Как декоратор, возвращающий дескриптор, узнаёт имя атрибута, под которым он установлен в классе?
Имя атрибута передаётся дескриптору автоматически через специальный метод set_name во время создания класса. Python вызывает его с владельцем и именем атрибута после формирования пространства имён класса.
До появления set_name дескриптору приходилось получать имя атрибута косвенно: через дополнительный аргумент декоратора, соглашения об именовании или ручную настройку после создания класса. В Python 3.6 был добавлен механизм автоматической уведомительной настройки дескрипторов, чтобы такие объекты могли корректно работать независимо от имени поля.
Декоратор может заменить функцию объектом, который реализует протокол дескриптора. Такой объект часто должен знать имя метода для журналирования, построения сообщений об ошибках, хранения данных или регистрации обработчика.
Если имя не сохранить при создании класса, объекту придётся анализировать функцию или получать имя из внешних соглашений. Это ненадёжно: имя функции может отличаться от имени атрибута, а один и тот же дескриптор можно разместить под несколькими именами.
Когда тело класса завершено, Python просматривает его атрибуты. Для каждого объекта, у которого есть метод set_name, вызывается примерно такая логика: передаются класс-владелец и имя атрибута в виде строки.
Декоратор получает исходную функцию при применении @Logged, создаёт объект Logged, а затем этот объект помещается в пространство имён класса. Метод set_name вызывается уже на этапе создания класса, а не при первом обращении к методу.
Метод get в примере нужен для поведения метода: при обращении через экземпляр дескриптор получает экземпляр и вручную передаёт его исходной функции. Сам set_name только сообщает имя и класс; автоматическую передачу self он не выполняет.
Важное ограничение: при обычном присваивании дескриптора после создания класса __set_name__ автоматически не вызывается. Если это необходимо, его вызывают вручную. Кроме того, если один объект-дескриптор разместить под несколькими именами в одном классе, метод будет вызван несколько раз, и сохранённое имя может быть перезаписано последним вызовом.
В библиотеке аудита методы сервисных классов помечались декоратором. Декоратор возвращал вызываемый объект-дескриптор, который должен был записывать в журнал имя публичного метода.
Рассматривались два варианта. Извлекать имя из исходной функции было просто, но это не учитывало переименование атрибута в классе. Передавать имя явно в аргументах декоратора было надёжнее, однако увеличивало дублирование и риск рассинхронизации.
Выбрали set_name: имя берётся непосредственно из класса, где размещён декоратор. Это устранило ручные параметры и сохранило корректное поведение при переименовании атрибута; при динамическом добавлении методов команда отдельно вызывает __set_name__ или использует фабрику, которая выполняет такую настройку.
Нет. Он вызывается один раз для размещения дескриптора в пространстве имён класса во время создания класса. Последующие обращения используют get, поэтому инициализацию состояния нельзя ошибочно связывать с каждым вызовом метода.
Нет. set_name только сообщает имя и владельца. Чтобы объект участвовал в доступе к атрибуту, нужен дескрипторный метод, обычно get. Чтобы объект можно было вызвать напрямую, например как результат декорирования в других сценариях, нужен call; наличие одного из этих методов не заменяет остальные.
Автоматического вызова set_name не будет, поэтому ожидаемый атрибут с именем может отсутствовать. Если дескриптор поддерживает такую настройку, код должен явно вызвать descriptor.__set_name__(Class, "attribute_name"); иначе необходимо изменить способ динамической регистрации.