Метаклассы: шесть шагов создания класса, из которых видно, что делать метаклассом, а что уже не надо
Метакласс — это тип класса, и весь его протокол следует из одного этого. Порядок шагов рассыпан по трём разделам документации; собранный в колонку, он отвечает на все практические вопросы разом — включая тот, ради которого метаклассы обычно и не нужны.
Полное техническое изложение
TL;DR
У класса тоже есть тип, и это type. Объект — экземпляр своего класса,
класс — экземпляр своего метакласса; метакласс управляет созданием классов
примерно так же, как класс управляет созданием экземпляров. Отдельной сущности
в языке для этого нет: чтобы написать свой метакласс, наследуются от type.
Отсюда главное следствие: Class(...) — это вызов экземпляра метакласса, и
поэтому создание экземпляров перехватывают у метакласса, а не у класса. А само
создание класса — цепочка шагов, и порядок между ними важнее их списка:
__set_name__ у дескрипторов и __init_subclass__ у родителя вызываются
внутри создания объекта класса, то есть до __init__ метакласса, а
декоратор класса вызывается позже всех. Настоящая цена метакласса — тоже не та,
которой ждут: не время, а metaclass conflict у того, кто вашего метакласса не
выбирал.
Дальше — то, что отличает знающего от читавшего. Шагов шесть:
Meta.__prepare__— выдаёт словарь под тело- выполняется тело класса
Meta.__new__— создаётся объект класса__set_name__у дескрипторов ← внутриtype.__new____init_subclass__у родителя ← внутриtype.__new__Meta.__init__
Шаги 4 и 5 — внутри третьего, то есть до шестого. Метакласс сам по себе не
стоит ничего: класс с пустым метаклассом создаётся за 7,09 мкс против 6,98 —
разница в пределах шума. Плата берётся ровно за одно — перехват __call__:
223,3 нс на экземпляр против 59,6 (3.13.7). Поэтому с 3.6 (PEP 487) большинство
задач решаются __init_subclass__ и __set_name__. Метакласс остаётся нужен,
когда надо перехватить само выполнение тела класса или создание
экземпляров.
- как объявляют класс и как из него получают экземпляр;
- что один класс можно унаследовать от другого;
- что класс в Python — такое же значение, как число или строка: его можно положить в переменную и передать в функцию;
- что декоратор — это функция, которая получает объект и возвращает объект.
typeкак метакласс,__prepare__,type.__new__,__init_subclass__,__set_name__;- дескрипторы, порядок разрешения методов, конфликт метаклассов, PEP 487.
База: у класса тоже есть тип
Слово «метакласс» пугает сильнее, чем стоило бы, и почти всегда из-за одного пропущенного шага. Шаг такой: у класса тоже есть тип.
Про объекты это привычно. Написали x = 5 — у x есть тип, и это int.
Объявили class Order и создали order = Order() — у order есть тип, и это
Order. Вопрос, который обычно не задают: а какой тип у самого Order?
Ответ: type. Класс — такой же объект, как число или строка, и у него тоже
есть тип. Тип класса и называется метаклассом.
Дальше модель складывается в две строки, и запоминать надо именно их:
- объект — экземпляр своего класса;
- класс — экземпляр своего метакласса.
Отсюда — ответ уровня Junior, который можно произнести на собеседовании целиком: метакласс управляет созданием классов примерно так же, как класс управляет созданием экземпляров. Класс решает, каким получится объект; метакласс решает, каким получится класс.
На этом уровне достаточно. Если на собеседовании спросили «что такое метакласс», ответ выше — верный ответ, а не его упрощённая версия: он ничего не искажает и не обещает сверх того, что есть, и договаривать до конца необходимости нет. Урок помечен как продвинутый не поэтому. Всё дальнейшее — про то, из каких шагов складывается «создание класса», в какой момент можно вмешаться в каждый и почему на практике вмешиваться почти никогда не нужно.
Механизм 1: класс — экземпляр метакласса, и отсюда весь протокол
Проверяется это одной строкой — и заодно видно, что цепочка на type и
кончается:
class Plain:
pass
print(type(Plain)) # <class 'type'>
print(type(type)) # <class 'type'>Plain — объект. Его тип — type. Метакласс — это просто тип, чьи экземпляры —
классы; чтобы написать свой, наследуются от type. Никакой отдельной сущности
в языке для метаклассов нет.
Отсюда сразу следует неочевидное: Class(...) — это вызов экземпляра
метакласса. Поэтому перехватить создание экземпляров можно, определив
__call__ у метакласса, а не у класса. Ровно так делают синглтоны:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]Механизм 2: шесть шагов, и порядок между ними важнее их списка
Справочник перечисляет шаги коротко.
When a class definition is executed, the following steps occur: MRO entries are
resolved; the appropriate metaclass is determined; the class namespace is
prepared; the class body is executed; the class object is created.
Когда выполняется определение класса, происходят следующие шаги: разрешаются элементы MRO; определяется подходящий метакласс; подготавливается пространство имён класса; выполняется тело класса; создаётся объект класса.
А два самых полезных хука описаны отдельно, в другом разделе, — и именно из-за этого их место в порядке обычно угадывают.
The type.__new__ method collects all of the attributes in the class namespace
that define a __set_name__ method; Those __set_name__ methods are called
with the class being defined and the assigned name of that particular
attribute; The __init_subclass__ hook is called on the immediate parent of
the new class in its method resolution order.
Метод type.__new__ собирает все атрибуты пространства имён класса, определяющие метод __set_name__; эти методы __set_name__ вызываются с определяемым классом и назначенным именем конкретного атрибута; хук __init_subclass__ вызывается у ближайшего родителя нового класса в его порядке разрешения методов.
Ключевое здесь — чьё это действие: оба хука вызывает сам type.__new__, то
есть они происходят внутри третьего шага, а не после него.
Что из этого следует практически:
__init_subclass__не увидит того, что метакласс делает в__init__— тот шаг ещё впереди. А метакласс в__init__уже видит результат обоих хуков.- Имя, базы и пространство имён можно менять только до третьего шага. Дальше они перестают быть аргументами и становятся свойствами существующего класса.
- Декоратор класса вызывается последним, когда всё уже готово:
After the class object is created, it is passed to the class decorators included in the class definition
(После создания объекта класса он передаётся декораторам класса, указанным в определении класса).
Что «последним» значит для декоратора класса
Из «вызывается последним» легко вывести «поэтому ничего не может», и это
неверно. Он получает готовый объект класса — а с готовым классом можно
почти всё. Прогон bench/metaclass/decorator_capabilities.py:
__name__ после декоратора Переименован
новый атрибут виден добавлено декоратором
декоратор вернул другой класс: имя Подменённый
класс на object: __bases__ = (Base,) TypeError
класс на Base: __bases__ = (Another,) Another
Переименовать — может. Дописать и переписать атрибуты — может. Вернуть вместо
класса совсем другой объект, и имя в модуле свяжется с ним, — тоже может. С
базами сложнее, но запрет не про декоратор: присваивание __bases__ разрешено
не всегда (у класса, наследующего прямо от object, оно даёт TypeError), и
это правило самого type, а не следствие момента вызова.
Настоящая граница проходит по другому месту — по времени, а не по силе:
до type.__new__ | готовый класс | |
|---|---|---|
__prepare__: отображение, в которое пишется тело | метакласс | — |
аргументы type.__new__ (имя, базы, пространство имён) | метакласс | — |
что увидят __set_name__ и __init_subclass__ | метакласс | — |
| атрибуты, имя, замена объекта целиком | метакласс | декоратор |
Тот же прогон печатает порядок событий, и в нём видно, что тело класса
выполняется в отображении из __prepare__ задолго до декоратора:
__prepare__ дал своё отображение
в пространство имён кладут 'a'
в пространство имён кладут 'b'
Meta.__new__ видит имена: ['a', 'b']
декоратор: тело уже выполнено, класс собран
Механизм 3: что умеет только метакласс
Три умения, и все три следуют из порядка.
Первое — перехватить выполнение тела класса. __prepare__ выдаёт словарь,
в который тело будет писаться, и потому видит каждое присваивание отдельно.
Ничем другим этого не сделать: к моменту __init_subclass__ тело уже
выполнено, и на руках только результат.
class NoDuplicates(dict):
def __setitem__(self, key, value):
if key in self:
raise TypeError(f"имя {key!r} определено в теле класса дважды")
super().__setitem__(key, value)
class StrictMeta(type):
@classmethod
def __prepare__(mcls, name, bases, **kwargs):
return NoDuplicates()Обычный класс дубль имени пропускает молча — просто побеждает последнее
присваивание. С таким метаклассом получается TypeError на этапе определения
класса.
Второе — перехватить создание экземпляра через __call__. Это то, из чего
делают синглтоны, кеши экземпляров и подмену возвращаемого объекта.
Третье — подменить имя, базы или пространство имён. Всё это аргументы
Meta.__new__, то есть третьего шага; к моменту __init_subclass__ и тем
более декоратора класс уже создан, и менять их поздно.
Всё остальное, за чем обычно тянутся к метаклассу, — регистрация подклассов, проверка обязательных атрибутов, автоматические имена дескрипторов — закрывается двумя хуками из PEP 487.
Строку про имя, базы и пространство имён в этой таблице стоит читать точно. Речь в ней про них как про аргументы создания класса: подсунуть своё отображение под тело, подменить имя или базы до того, как класс собран, может только метакласс. Готовому классу декоратор имя переставит — это показано выше прогоном. Граница между двумя инструментами проходит по времени, а не по силе.
Механизм 4: граница __init_subclass__
Хук вызывается у родителя, когда создаётся потомок. Справочник сравнивает его с декоратором прямо:
This is closely related to class decorators, but where class decorators only
affect the specific class they're applied to, __init_subclass__ solely applies
to future subclasses of the class defining the method.
Это тесно связано с декораторами классов, но если декораторы влияют только на конкретный класс, к которому применены, то __init_subclass__ действует исключительно на будущие подклассы класса, определяющего этот метод.
Слово «будущие» — это и есть граница:
class Registry:
registered = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Registry.registered.append(cls.__name__)
class First(Registry): pass
class Second(Registry): pass
print(Registry.registered) # ['First', 'Second'] — сам Registry не попалМетакласс, наоборот, срабатывает и на том классе, который его указал:
Meta.__new__ вызывается для базового класса так же, как для наследников.
Если реестр обязан включать базовый класс — это одна из немногих причин взять
метакласс.
И ещё одно: super().__init_subclass__(**kwargs) в первой строке — не
формальность. Без него выполнится хук ближайшего родителя, а у всех классов
дальше по MRO — нет, и молча: ни ошибки, ни предупреждения не будет.
Глубже: что метакласс стоит на самом деле
__call__, а не за метакласс сам по себе.Не время. Класс с пустым метаклассом создаётся за 7,09 мкс против 6,98 мкс без него (3.13.7, лучшее из семи прогонов) — разница в пределах шума, и платится она один раз за класс, а не за экземпляр.
Время берётся ровно за одно: перехват __call__ добавляет вызов метода на
Python в путь, который иначе идёт целиком в C. 223,3 нс на экземпляр против
59,6 — в 3,7 раза, и на каждом создании. Замер сделан на пустом классе, так
что добавка здесь постоянная: у класса, который что-то делает в __init__,
та же надбавка весит заметно меньше. Одиночка, сделанный
метаклассом, платит эту цену при каждом обращении, включая те, где объект уже
создан.
Настоящая же цена — вот эта:
The most derived metaclass is one which is a subtype of all of these candidate
metaclasses. If none of the candidate metaclasses meets that criterion, then the
class definition will fail with TypeError.
Наиболее производный метакласс — это тот, который является подтипом всех кандидатов. Если ни один из кандидатов этому критерию не удовлетворяет, определение класса завершится с TypeError.
TypeError: metaclass conflict: the metaclass of a derived class must be
a (non-strict) subclass of the metaclasses of all its bases
Заметьте, где эта ошибка возникает: в чужом файле — у того, кто пишет
наследника и вашего метакласса не выбирал. Он всего лишь захотел
унаследоваться и от вашего класса, и от чего-то ещё со своим метаклассом —
например, от ABC или от Enum. Починить это он может только двумя
способами: не наследоваться либо написать третий метакласс, объединяющий два
чужих.
Как отвечать на собеседовании
Короткий ответ: метакласс — это тип класса. Класс — экземпляр метакласса
ровно так же, как объект — экземпляр класса, и метакласс управляет созданием
классов примерно так же, как класс управляет созданием экземпляров. Отсюда и
Class(...) — это вызов __call__ у метакласса, потому что класс его
экземпляр.
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
Своими силами метакласс умеет две вещи, которых не умеет больше ничто:
перехватить выполнение тела класса через __prepare__ и перехватить создание
экземпляра через __call__. Самые частые задачи — регистрация подклассов,
проверка их полей, имя дескриптора — с 3.6 решаются __init_subclass__ и
__set_name__ дешевле и без конфликта метаклассов. Это совет по
проектированию, а не равенство возможностей: подготовка пространства имён,
замена баз и перехват создания экземпляра остаются только у метакласса.
Одна формулировка, на которой легко ошибиться вслух, — про декоратор класса.
Сказать «декоратор не может заменить класс» неверно: заменить как раз может —
он вправе вернуть вместо класса другой объект, и имя в модуле свяжется с ним.
Его граница в другом: он не участвует в создании класса. Отображение, в
котором выполнялось тело, и аргументы type.__new__ к его приходу уже история,
и повлиять на то, что увидят __set_name__ и __init_subclass__, он не может.
Разница между ним и метаклассом — по времени вмешательства, а не по силе.
Если спросят «когда вы последний раз писали метакласс» — честный ответ обычно «не писал», и он правильный. Хороший признак того, что вопрос понят: назвать не время как цену, а конфликт метаклассов, и добавить, что платит за него чужой код. И держать границу у замеров: 7,09 против 6,98 мкс и 223,3 против 59,6 нс сняты на одной машине и на пустом классе (3.13.7) — содержательно в них не число, а то, за что именно берётся плата.
Дальше спросят
Всё то же самое можно сделать через __init_subclass__?
Почти всё, что обычно и хотят, — да, и тогда метакласс не нужен. Граница
проходит там, где требуется вмешаться в само создание класса, а не отреагировать
на уже созданный наследник: __init_subclass__ вызывается, когда класс уже
собран.
Метакласс замедляет работу с классом?
Сам по себе — нет; платит он на создании класса, а не на обращениях к нему. Замедляет конкретное действие, а не факт наличия метакласса, — и в уроке это разделено замером.
Частые заблуждения
Метаклассы медленные
Создание класса с пустым метаклассом — 7,09 мкс против 6,98 без него, разница в пределах шума, и платится один раз за класс. Время берётся только за перехват __call__: 223,3 нс на экземпляр против 59,6 (3.13.7), то есть в 3,7 раза, и уже на КАЖДОМ создании. Настоящая цена метакласса вообще не во времени, а в конфликте метаклассов у чужого кода.
__init_subclass__ — это просто удобная замена метакласса
Замена в самых частых случаях, но не полная. Он не срабатывает на том классе, где объявлен, — базовый класс в собственный реестр не попадает. Он не участвует в создании класса: пространство имён, в котором выполняется тело, и аргументы type.__new__ к его вызову уже позади (менять атрибуты готового класса он, как и декоратор, может). И он не видит выполнения тела класса: __prepare__ бывает только у метакласса.
__set_name__ и __init_subclass__ вызываются после того, как класс готов
Они вызываются ВНУТРИ type.__new__, то есть между созданием объекта класса и Meta.__init__. Это записано в справочнике и проверяется счётчиком. Отсюда практический ответ: __init_subclass__ не увидит того, что метакласс делает в __init__, — а декоратор класса, наоборот, вызывается позже всех.
Метакласс — это то же самое, что декоратор класса, только сложнее
Декоратор применяется ровно к тому классу, над которым написан: наследник его не получает, и в порядке вызовов для наследника декоратора нет вовсе. Метакласс наследуется. И разница между ними не в силе, а во времени: декоратор получает готовый класс — переименовать, дописать атрибуты и даже вернуть вместо него другой объект он может, — но пространство имён, в котором выполнялось тело, и аргументы type.__new__ к его приходу уже история.
Конфликт метаклассов — редкая экзотика
Он возникает при попытке унаследоваться и от вашего класса, и от чего-то со своим метаклассом — а это ABC и Enum, то есть стандартная библиотека. И возникает он в ЧУЖОМ файле, у автора наследника: сообщение «metaclass conflict» получит он, а не вы.
История версий
| Версия | Изменение | Что это значит для кода |
|---|---|---|
| 3.0 | PEP 3115 вводит __prepare__ и нынешний синтаксис metaclass= вместо атрибута __metaclass__. С этого момента у метакласса появляется единственное умение, которого нет ни у чего другого: перехватить выполнение тела класса. | |
| 3.6 | PEP 487: появляются __init_subclass__ и __set_name__. Это главная строка таблицы — после неё большинство метаклассов писать больше не нужно. Регистрация подклассов, проверка обязательных атрибутов и передача дескриптору его имени решаются без метакласса и без риска конфликта. | |
| 3.11 | Опорная точка урока: порядок шести шагов, поведение __prepare__, граница __init_subclass__ и текст ошибки о конфликте на 3.11 такие же, как на 3.14.7. Проверено запуском одного скрипта на четырёх версиях. | |
| 3.12 | Появляется синтаксис class C[T] (PEP 695) — и метакласс начинает видеть то, чего автор класса не писал. Проверено: __prepare__ получает базы (typing.Generic,) у класса, объявленного без единой базы, а в пространство имён добавляются __type_params__ и __orig_bases__. | |
| 3.13 | В пространство имён, которое получает метакласс, добавляются __firstlineno__ и __static_attributes__. На 3.11 и 3.12 там было ровно __module__, __qualname__ и то, что написано в теле. Метакласс, перебирающий пространство имён и падающий на незнакомом ключе, ломается на этом обновлении — это единственная строка таблицы, способная сломать существующий код. |
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.
Практика · что напечатает
class Base:
registry = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Base.registry.append(cls.__name__)
class A(Base):
pass
class B(A):
pass
print(Base.registry)Практика · оцените
Проверьте себя
В каком порядке вызываются __init_subclass__ родителя и Meta.__init__ при создании класса?
Чем измерено
Числа этой статьи получены этими скриптами. Каждый открывается прямо отсюда — вместе с записью прогона: на чём считали, что получилось и с каким разбросом.
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- У класса тоже есть тип, и это
type. Объект — экземпляр своего класса, класс — экземпляр своего метакласса; метакласс управляет созданием классов примерно так же, как класс управляет созданием экземпляров. Отдельной сущности в языке для этого нет: чтобы написать свой метакласс, наследуются отtype. - Отсюда главное следствие:
Class(...)— это вызов экземпляра метакласса, и поэтому создание экземпляров перехватывают у метакласса, а не у класса. А само создание класса — цепочка шагов, и порядок между ними важнее их списка:__set_name__у дескрипторов и__init_subclass__у родителя вызываются внутри создания объекта класса, то есть до__init__метакласса, а декоратор класса вызывается позже всех. Настоящая цена метакласса — тоже не та, которой ждут: не время, аmetaclass conflictу того, кто вашего метакласса не выбирал. - Дальше — то, что отличает знающего от читавшего. Шагов шесть:
- 1.
Meta.__prepare__— выдаёт словарь под тело 2. выполняется тело класса 3.Meta.__new__— создаётся объект класса 4.__set_name__у дескрипторов ← внутриtype.__new__5.__init_subclass__у родителя ← внутриtype.__new__6.Meta.__init__ - Шаги 4 и 5 — внутри третьего, то есть до шестого. Метакласс сам по себе не стоит ничего: класс с пустым метаклассом создаётся за 7,09 мкс против 6,98 — разница в пределах шума. Плата берётся ровно за одно — перехват
__call__: 223,3 нс на экземпляр против 59,6 (3.13.7). Поэтому с 3.6 (PEP 487) большинство задач решаются__init_subclass__и__set_name__. Метакласс остаётся нужен, когда надо перехватить само выполнение тела класса или создание экземпляров.
На самом деле
- Создание класса с пустым метаклассом — 7,09 мкс против 6,98 без него, разница в пределах шума, и платится один раз за класс. Время берётся только за перехват
__call__: 223,3 нс на экземпляр против 59,6 (3.13.7), то есть в 3,7 раза, и уже на КАЖДОМ создании. Настоящая цена метакласса вообще не во времени, а в конфликте метаклассов у чужого кода. - Замена в самых частых случаях, но не полная. Он не срабатывает на том классе, где объявлен, — базовый класс в собственный реестр не попадает. Он не участвует в создании класса: пространство имён, в котором выполняется тело, и аргументы
type.__new__к его вызову уже позади (менять атрибуты готового класса он, как и декоратор, может). И он не видит выполнения тела класса:__prepare__бывает только у метакласса. - Они вызываются ВНУТРИ
type.__new__, то есть между созданием объекта класса иMeta.__init__. Это записано в справочнике и проверяется счётчиком. Отсюда практический ответ:__init_subclass__не увидит того, что метакласс делает в__init__, — а декоратор класса, наоборот, вызывается позже всех. - Декоратор применяется ровно к тому классу, над которым написан: наследник его не получает, и в порядке вызовов для наследника декоратора нет вовсе. Метакласс наследуется. И разница между ними не в силе, а во времени: декоратор получает готовый класс — переименовать, дописать атрибуты и даже вернуть вместо него другой объект он может, — но пространство имён, в котором выполнялось тело, и аргументы
type.__new__к его приходу уже история. - Он возникает при попытке унаследоваться и от вашего класса, и от чего-то со своим метаклассом — а это
ABCиEnum, то есть стандартная библиотека. И возникает он в ЧУЖОМ файле, у автора наследника: сообщение «metaclass conflict» получит он, а не вы.
По версиям
- 3.0
- PEP 3115 вводит
__prepare__и нынешний синтаксисmetaclass=вместо атрибута__metaclass__. С этого момента у метакласса появляется единственное умение, которого нет ни у чего другого: перехватить выполнение тела класса.< - 3.6
- PEP 487: появляются
__init_subclass__и__set_name__. Это главная строка таблицы — после неё большинство метаклассов писать больше не нужно. Регистрация подклассов, проверка обязательных атрибутов и передача дескриптору его имени решаются без метакласса и без риска конфликта.< - 3.11
- Опорная точка урока: порядок шести шагов, поведение
__prepare__, граница__init_subclass__и текст ошибки о конфликте на 3.11 такие же, как на 3.14.7. Проверено запуском одного скрипта на четырёх версиях.< - 3.12
- Появляется синтаксис
class C[T](PEP 695) — и метакласс начинает видеть то, чего автор класса не писал. Проверено:__prepare__получает базы(typing.Generic,)у класса, объявленного без единой базы, а в пространство имён добавляются__type_params__и__orig_bases__.< - 3.13
- В пространство имён, которое получает метакласс, добавляются
__firstlineno__и__static_attributes__. На 3.11 и 3.12 там было ровно__module__,__qualname__и то, что написано в теле. Метакласс, перебирающий пространство имён и падающий на незнакомом ключе, ломается на этом обновлении — это единственная строка таблицы, способная сломать существующий код.<
Что разобрано
- База: у класса тоже есть тип
- Механизм 1: класс — экземпляр метакласса, и отсюда весь протокол
- Механизм 2: шесть шагов, и порядок между ними важнее их списка
- Механизм 3: что умеет только метакласс
- Механизм 4: граница `__init_subclass__`
- Глубже: что метакласс стоит на самом деле
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- История версий
- Практика
- Проверьте себя
- Чем измерено
Источники и что читать дальше
6 ИСТОЧНИКОВ
- Модель данных — создание классаОфициальная документация. Первоисточник порядка шагов. Дословно: «When a class definition is executed, the following steps occur: MRO entries are resolved; the appropriate metaclass is determined; the class namespace is prepared; the class body is executed; the class object is created» (Когда выполняется определение класса, происходят следующие шаги: разрешаются элементы MRO; определяется подходящий метакласс; подготавливается пространство имён класса; выполняется тело класса; создаётся объект класса). Ключевое здесь — что тело выполняется ПОСЛЕ подготовки пространства имён и ДО создания объекта класса.https://docs.python.org/3.14/reference/datamodel.html#metaclasses
- Модель данных — что происходит внутри type.__new__Официальная документация. Откуда взято, что __set_name__ и __init_subclass__ вызываются внутри создания класса, а не после него: «The type.__new__ method collects all of the attributes in the class namespace that define a __set_name__ method; Those __set_name__ methods are called with the class being defined and the assigned name of that particular attribute; The __init_subclass__ hook is called on the immediate parent of the new class in its method resolution order» (Метод type.__new__ собирает все атрибуты пространства имён класса, определяющие метод __set_name__; эти методы __set_name__ вызываются с определяемым классом и назначенным именем конкретного атрибута; хук __init_subclass__ вызывается у ближайшего родителя нового класса в его порядке разрешения методов). Там же про декоратор: «After the class object is created, it is passed to the class decorators included in the class definition» (После создания объекта класса он передаётся декораторам класса, указанным в определении класса).https://docs.python.org/3.14/reference/datamodel.html#creating-the-class-object
- Модель данных — выбор метакласса и конфликтОфициальная документация. Точная формулировка условия, из-за которого возникает metaclass conflict: «The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError» (Наиболее производный метакласс — это тот, который является подтипом ВСЕХ кандидатов. Если ни один из кандидатов этому критерию не удовлетворяет, определение класса завершится с TypeError). Отсюда же следует, что ошибка возникает в момент определения класса-наследника.https://docs.python.org/3.14/reference/datamodel.html#determining-the-appropriate-metaclass
- Модель данных — __init_subclass__ и его отличие от декоратораОфициальная документация. Прямое сравнение двух инструментов, взятое в урок целиком: «This is closely related to class decorators, but where class decorators only affect the specific class they're applied to, __init_subclass__ solely applies to future subclasses of the class defining the method» (Это тесно связано с декораторами классов, но если декораторы влияют только на конкретный класс, к которому применены, то __init_subclass__ действует исключительно на БУДУЩИЕ подклассы класса, определяющего этот метод). Слово «будущие» и объясняет, почему сам определяющий класс в реестр не попадает.https://docs.python.org/3.14/reference/datamodel.html#object.__init_subclass__
- PEP 3115 — Metaclasses in Python 3000PEP. Документ, которым появился __prepare__ — единственный хук, дающий метаклассу то, чего нет ни у одного другого инструмента. Автор Талин, статус Final, Python 3.0. Нужен, чтобы показать: перехват выполнения тела класса — не побочный эффект реализации, а заявленная цель.https://peps.python.org/pep-3115/
- PEP 487 — Simpler customisation of class creationPEP. Документ, которым в 3.6 появились __init_subclass__ и __set_name__. Мартин Тайхман и Ник Коглан, Final. Именно он и есть причина, по которой большинство метаклассов сегодня писать не нужно: два хука закрывают самые частые применения, не втягивая класс в конфликт метаклассов.https://peps.python.org/pep-0487/