При создании класса метакласс задаёт специальное пространство имён: как это меняет выполнение тела класса?
До выполнения тела класса Python вызывает у метакласса метод __prepare__ и получает пространство имён. Все присваивания в теле класса записываются в этот объект, после чего его содержимое передаётся метаклассу для создания самого класса.
Поэтому метакласс может не только изменить готовый класс, но и влиять на сбор атрибутов уже во время выполнения тела: сохранять порядок, проверять имена, регистрировать объявления или реализовывать предметно-ориентированный синтаксис.
Раньше выбор пространства имён для тела класса не был доступен метаклассу как отдельный стандартный этап. Это ограничивало создание декларативных API, где важны порядок объявлений и обработка атрибутов непосредственно во время определения класса.
Механизм __prepare__, введённый в рамках расширения модели метаклассов Python 3, отделил подготовку пространства имён от последующего создания класса. Благодаря этому метакласс может предоставить специальное отображение вместо обычного словаря.
Обычный класс обычно достаточно создать из словаря с атрибутами. Но фреймворку может потребоваться отличать объявления полей от обычных переменных, запрещать дублирование имён или сохранять порядок полей до создания класса.
Если обрабатывать такие данные только после создания класса, часть информации уже может быть потеряна, а ошибки обнаружатся позднее. Неправильная реализация метакласса также способна нарушить служебные записи класса или изменить ожидаемое поведение тела класса.
Последовательность выглядит так:
__prepare__.type.__new__, который создаёт объект класса.При выполнении тела User присваивания проходят через Namespace.__setitem__. В реальном фреймворке вместо печати можно регистрировать объявления полей, проверять конфликт имён или сохранять метаданные.
__prepare__ возвращает отображение, но не обязан возвращать обычный dict. Однако итоговый метакласс должен корректно передать пространство имён в механизм создания класса; произвольное отображение может потребовать преобразования в словарь. Пространство имён также содержит служебные записи вроде __module__ и __qualname__.
Порядок объявлений сохраняется только при использовании структуры, которая его поддерживает. Сам факт наличия __prepare__ не гарантирует порядок: пользовательский объект может его не сохранять или метакласс может изменить данные перед вызовом type.__new__.
Особенно важна запись __classcell__. Компилятор добавляет её, когда метод использует __class__ или нулевой аргумент super(). Если пользовательский метакласс удалит или не передаст её в type.__new__, такой метод может завершиться ошибкой во время выполнения.
Для простой валидации уже созданных классов чаще подходит __init_subclass__: он проще и не вмешивается в выполнение тела класса. __prepare__ оправдан, когда нужно влиять именно на процесс объявления атрибутов, их порядок или обработку присваиваний.
Фреймворк описывает схему сообщения через атрибуты класса. Порядок полей определяет порядок сериализации, а повторное имя должно считаться ошибкой ещё при объявлении класса.
Вариант с последующим обходом __dict__ прост и прозрачен, но плохо подходит, если требуется перехватывать присваивания и выдавать точные ошибки в момент построения класса. Вариант с декоратором класса позволяет обработать готовый класс, но добавляет обязательный шаг и не контролирует само выполнение тела.
Выбранный __prepare__ возвращает отображение, которое регистрирует имена полей и их порядок, а затем метакласс проверяет записи и создаёт класс. Это даёт раннюю диагностику и предсказуемую сериализацию, но усложняет реализацию и требует аккуратно сохранять служебные элементы пространства имён.
1. Можно ли заменить __prepare__ на __init_subclass__, если нужно проверять присваивания в теле класса?
Нет, не полностью. __init_subclass__ вызывается после создания подкласса, когда тело уже выполнено и класс построен. Он подходит для проверки готового класса и настройки наследников, но не позволяет перехватывать каждое присваивание или заменить локальное пространство имён тела.
2. Что произойдёт, если метакласс удалит __classcell__?
Методы, которым нужен контекст класса, например использующие нулевой аргумент super(), могут завершиться ошибкой при вызове. Поэтому метакласс должен сохранять __classcell__ и передавать его в type.__new__, если он был создан компилятором.
3. Гарантирует ли сам __prepare__ порядок атрибутов класса?
Нет. Порядок зависит от конкретного возвращённого отображения и последующей обработки пространства имён. Обычный словарь в современных версиях Python сохраняет порядок вставки, но специальное отображение может сортировать, фильтровать или иным образом перестраивать записи.