Кто перехватывает присваивание атрибута самому классу: его __setattr__ или __setattr__ метакласса?
Присваивание атрибута самому классу перехватывает метакласс: вызывается его __setattr__, обычно унаследованный от type. Метод __setattr__, объявленный внутри обычного класса, относится к его экземплярам и срабатывает при присваивании атрибутов этим экземплярам.
В модели объектов Python класс сам является объектом. Поэтому у него есть собственный тип — метакласс, который управляет поведением объектов-классов.
Такой подход позволяет централизованно задавать правила создания классов и изменения их атрибутов: например, регистрировать методы, запрещать изменение схемы класса или синхронизировать метаданные.
Логика в __setattr__ обычного класса не будет автоматически выполняться при изменении самого класса. Если разработчик ожидает, что присваивание атрибута классу вызовет этот метод, контроль изменений будет установлен не на том уровне.
Ошибка особенно опасна в ORM, системах регистрации плагинов и библиотеках, где атрибуты класса используются как декларативная конфигурация. В результате метаданные могут не обновиться, а некорректное изменение класса останется незамеченным.
Для операции над экземпляром действует цепочка вроде экземпляр.__setattr__(имя, значение). Для операции над классом объектом-целью является сам класс, поэтому Python обращается к type(класс).__setattr__, то есть к метаклассу.
Например, если C — класс, то присваивание C.x = 1 вызывает __setattr__ метакласса type(C). Если специальный метод метакласса не переопределён, используется реализация type.__setattr__, которая обычно записывает атрибут в пространство имён класса.
Первое присваивание проходит через Meta.__setattr__, второе — через C.__setattr__. Вызов super().__setattr__ важен: без него новое значение не будет передано стандартной реализации, если это не сделано намеренно.
При проектировании метакласса нужно учитывать, что его логика затрагивает все классы, использующие этот метакласс. Кроме того, несколько базовых классов с несовместимыми метаклассами могут привести к конфликту метаклассов при создании нового класса.
В системе плагинов каждый обработчик объявляется атрибутом класса. После объявления или динамического добавления обработчика нужно обновить общий реестр.
Первый вариант — переопределить __setattr__ у базового класса. Он будет отслеживать только изменения экземпляров и не увидит присваивание вроде Plugin.handle = handler.
Второй вариант — периодически просматривать __dict__ всех классов. Это не перехватывает изменения в момент выполнения, усложняет синхронизацию и может потребовать явного обновления реестра.
Выбранный вариант — метакласс с переопределённым __setattr__, который после стандартного присваивания проверяет имя атрибута и обновляет реестр. Такой подход сразу реагирует на динамические изменения, но его следует применять умеренно: скрытая логика изменения класса усложняет отладку, поэтому правила регистрации должны быть явно документированы.
__setattr__ класса при присваивании атрибута его экземпляру?Нет, если речь идёт о методе, объявленном в самом классе. Для операции над экземпляром этот метод как раз является обработчиком: Python вызывает его при присваивании атрибута экземпляру. При этом реализация может делегировать работу object.__setattr__ или выполнять собственную валидацию.
__setattr__, но не вызвал базовую реализацию?Присваивание может быть полностью подавлено: новое значение не попадёт в пространство имён класса. Это иногда используется для неизменяемых классов, но чаще является ошибкой. Если метакласс должен только проверить или зарегистрировать изменение, обычно после проверки вызывают super().__setattr__.
Да. Атрибуты метакласса участвуют в управлении доступом к атрибутам объекта-класса, а дескриптор метакласса может обработать соответствующую операцию. Поэтому для полного анализа нужно учитывать не только __setattr__ метакласса, но и дескрипторы, объявленные в самом метаклассе, а также стандартную реализацию type.