Что произойдёт с дескриптором, добавленным в класс после его создания, если он рассчитывает на автоматический вызов __set_name__?
Автоматически ничего не произойдёт: после создания класса Python не вызывает __set_name__ при динамическом присваивании дескриптора атрибуту класса. Если дескриптору нужны имя атрибута и владелец, их следует передать вручную вызовом __set_name__ либо использовать собственный механизм регистрации.
Дескрипторы позволяют объекту управлять доступом к атрибутам через методы __get__, __set__ и __delete__. Позже в протокол добавили __set_name__, чтобы дескриптор при создании класса мог узнать имя атрибута без ручного дублирования этого имени в его настройках.
Этот механизм рассчитан прежде всего на этап создания класса. Такой дизайн упрощает реализацию дескрипторов и уменьшает необходимость в сложных метаклассах, но не превращает любое последующее изменение класса в повторное выполнение его тела.
Если дескриптор добавляется в класс динамически, его __set_name__ не вызывается автоматически. Поэтому внутренние поля, например имя атрибута или ссылка на класс-владелец, могут остаться неинициализированными.
Последствие зависит от реализации дескриптора: обращение к атрибуту может завершиться AttributeError, некорректно использовать общее имя для всех экземпляров или незаметно записывать данные не туда. Особенно рискован такой подход в ORM, валидаторах и системах конфигурации, где дескрипторы регистрируют метаданные.
Во время создания класса механизм построения класса обнаруживает дескрипторы в пространстве имён и вызывает их __set_name__(owner, name). В этот момент owner — создаваемый класс, а name — имя атрибута, под которым дескриптор объявлен.
Обычное присваивание атрибута уже существующему классу проходит через установку атрибута класса, но не запускает автоматическую фазу __set_name__. Сам дескриптор при этом всё равно может начать участвовать в последующих обращениях к атрибуту, просто он будет использоваться в неинициализированном состоянии.
Минимальный пример:
После Model.title = field атрибут уже находится в классе, однако field.name ещё не существует. Явный вызов инициализирует дескриптор, после чего его поведение становится таким же, как если бы он был объявлен в теле класса.
Ручной вызов должен выполняться ровно один раз для нужной пары владельца и имени. Если дескриптор переиспользуется в нескольких классах, нельзя бездумно хранить единственные owner и name: они могут быть перезаписаны. В таком случае метаданные лучше хранить отдельно для каждого владельца или создавать отдельный экземпляр дескриптора.
Альтернатива — добавлять дескрипторы до завершения создания класса через метакласс или другую фабрику классов. Это позволяет сохранить стандартный жизненный цикл, но усложняет архитектуру и может быть избыточно для единичного динамического изменения.
В системе валидации поля модели обычно объявляются статически, поэтому __set_name__ вызывается автоматически. Позже появилась возможность подключать поля из конфигурации после создания класса. Простое присваивание поля в класс прошло без ошибки, но первая валидация завершилась исключением: дескриптор обращался к отсутствующему имени.
Рассматривались два варианта. Перестраивать весь класс через метакласс было бы единообразно, но дорого по сложности и рискованно для совместимости. Вызывать __set_name__ сразу после присваивания проще, однако ответственность за корректный порядок операций и повторную регистрацию ложится на код загрузки конфигурации.
Выбрали второй вариант: фабрика динамических полей выполняла присваивание, затем явно вызывала __set_name__ и проверяла отсутствие дублирующего имени. Это сохранило текущую архитектуру и устранило ошибку, не добавляя глобальный метакласс.
setattr для класса __set_name__ автоматически?Нет. setattr изменяет пространство имён класса, но не запускает процедуру уведомления, связанную с первоначальным созданием класса. Если дескриптор добавлен таким способом, __set_name__ нужно вызвать явно или поручить это собственной обёртке.
__set_name__, если дескриптор уже использовался до этого?Не всегда. Вызов может изменить внутреннее состояние дескриптора после того, как он уже обслуживал обращения, поэтому поведение зависит от его реализации. Надёжнее регистрировать дескриптор до первого использования класса и отдельно продумать последствия повторной регистрации.
Новый объект не получит автоматический вызов __set_name__, а старый дескриптор не получит уведомление об удалении или замене. Если дескрипторы ведут внешнюю регистрацию, кэш или список полей, код замены должен самостоятельно обновить все связанные структуры; одного присваивания атрибута недостаточно.