Класс — объект, значит у него есть тип. Этот тип и есть метакласс. Из одного факта выводятся и Django ORM, и pydantic, и ABC — и понимание, что в девяти случаях из десяти метакласс не нужен.
Зафиксируй вывод и причину до того, как откроешь ответ.
print(type(int))
print(type(type))
X = type("X", (object,), {"v": 1})
print(X, X().v)<class 'type'>
<class 'type'>
<class '__main__.X'> 1Класс создан вызовом функции. И тип типа — он сам.class Plugin:
registry = []
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
Plugin.registry.append(cls.__name__)
class A(Plugin): pass
class B(Plugin): pass
print(Plugin.registry)['A', 'B']Реестр наполнился сам, без метакласса и без декоратора.class Field:
def __set_name__(self, owner, name):
print("set_name", owner.__name__, name)
class M:
f = Field()set_name M fДескриптор узнал своё имя в момент создания класса — хотя ему его никто не передавал.from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def go(self): ...
class OtherMeta(type): pass
class Conflict(Base, metaclass=OtherMeta): passTypeError: metaclass conflict: the metaclass of a derived
class must be a (non-strict) subclass of the metaclasses
of all its basesОшибка при объявлении класса, ещё до всякого использования.Из урока 06 известно: class — исполняемая инструкция, а собранный словарь имён передаётся конструктору типа. Здесь мы называем этот конструктор по имени.
Класс — объект, значит у него есть тип. Тип класса называется метаклассом. По умолчанию это type.
type с тремя аргументами создаёт класс: имя, кортеж баз, словарь имён. Инструкция class — синтаксис для этого вызова.
Метакласс — класс, наследующий type. Переопределяя его методы, вклиниваешься либо в создание класса, либо в создание экземпляров.
Фрагмент A. type(int) — это type, потому что int создан им. А type(type) — снова type: цепочка «объект → его тип» замыкается на себя, иначе она была бы бесконечной. Это единственное место в языке, где такое замыкание есть, и сделано оно ровно чтобы модель осталась конечной.
И раз класс создаётся вызовом, его можно создать вручную в любой момент — что и делают все библиотеки, генерирующие классы из схемы, конфига или таблицы БД.
Метакласс даёт два принципиально разных крючка, и их регулярно путают.
__new__ и __init__ метакласса вызываются при создании класса — один раз, когда исполняется class. Сюда вешают проверку структуры, регистрацию, преобразование объявленных полей.
__call__ метакласса вызывается при создании экземпляра: именно он потом дёргает __new__ и __init__ самого класса. Отсюда классический синглтон — перехватить __call__ и вернуть один и тот же объект.
Ошибка «положил логику не в тот метод» даёт код, который работает один раз или, наоборот, на каждом вызове — и выглядит необъяснимо, пока не помнишь, что это разные моменты жизни.
Фрагменты B и C показывают то, что закрывает большинство реальных задач без метакласса.
__init_subclass__ вызывается на ближайшем предке при создании подкласса — этого достаточно для реестров плагинов, проверки обязательных атрибутов, автоматической регистрации обработчиков. __set_name__ вызывается на каждом дескрипторе в теле класса и сообщает ему имя и владельца — так поля моделей узнают, как их назвали.
Полная лестница по возрастанию цены: декоратор класса (получает готовый класс, проще всего) → __init_subclass__ и __set_name__ (встроенные крючки, наследуются) → метакласс (меняет сам процесс создания). Выбирать надо самое левое, что решает задачу.
Фрагмент D. У класса ровно один метакласс, и он обязан быть подклассом метаклассов всех баз. ABC использует ABCMeta; попытка добавить свой метакласс к абстрактному базовому классу даёт конфликт при объявлении.
Лечится наследованием — class MyMeta(ABCMeta), — но сам факт важнее лечения: метакласс — это заявка на монополию. Библиотека, использующая свой метакласс, плохо смешивается с другой такой же. Именно поэтому __init_subclass__ и добавили в 3.6: он наследуется и не конфликтует.
Метакласс наследуется. Он применяется ко всем подклассам автоматически, и отключить это нельзя. Класс, случайно унаследованный от чего-то с метаклассом, получает его поведение целиком.
__prepare__ нужен реже, чем кажется. Он задаёт словарь для тела класса; исторически применялся, чтобы запомнить порядок объявления. С 3.7 обычный словарь упорядочен 🔒, и большинство таких применений отпало — enum тому пример.
Порядок крючков фиксирован: тело исполняется → метакласс создаёт класс → вызываются __set_name__ у дескрипторов → вызывается __init_subclass__ у предка. Логика, зависящая от другого порядка, работать не будет.
Корень R2: всё резолвится в рантайме, и класс — обычный объект. Метакласс не отдельная возможность, а прямое следствие: раз класс объект, у него есть тип; раз тип можно заменить, процесс создания можно перехватить.
Механизм появился в 2001 году вместе с объединением типов и классов и десять лет оставался экспертным инструментом с репутацией «глубокой магии». Перелом случился в 3.6: __init_subclass__ и __set_name__ вынули из метаклассов две задачи, ради которых их писали в девяти случаях из десяти. Сегодня метакласс в прикладном коде — почти всегда признак того, что задачу можно решить проще.
Ближайшая точная аналогия — CRTP в C++: базовый шаблон, параметризованный собственным наследником, который в момент инстанцирования знает про потомка и может его зарегистрировать. Это ровно то, что делает __init_subclass__, только у C++ работу выполняет компилятор, а у Python — интерпретатор при исполнении class.
Разница в цене показательна. CRTP бесплатен в рантайме и дорог в диагностике: ошибка выражается сообщением на две страницы. __init_subclass__ стоит вызова при каждом объявлении класса и отлаживается обычным print. Один и тот же приём, две разные валюты.
Вопросы, которые возникают сами, если читать внимательно. Ответ — под вопросом.
Потому что за десять лет почти всё, ради чего их звали, переехало в более дешёвые механизмы.
Классические задачи метакласса: зарегистрировать подкласс в реестре, проверить, что у него есть нужные атрибуты, дать дескрипторам знать их имя, подставить сгенерированные методы. Сегодня для первых трёх есть __init_subclass__ и __set_name__ (обе с 3.6), а для четвёртой — декоратор класса.
class Plugin:
registry = []
def __init_subclass__(cls, /, name=None, **kw):
super().__init_subclass__(**kw)
cls.name = name or cls.__name__.lower()
Plugin.registry.append(cls)
class Csv(Plugin, name="csv"): pass
class Json(Plugin): pass
print([c.name for c in Plugin.registry]) # → ['csv', 'json']
Это читается, не требует объяснять метаклассы новому человеку и — главное — не конфликтует. Метакласс наследуется, и класс не может иметь два несовместимых метакласса: попытка унаследоваться от ABC и от своего класса с метаклассом даёт TypeError: metaclass conflict. Это единственная причина, по которой метакласс в библиотеке становится проблемой её пользователей.
Когда метакласс всё-таки нужен: изменить процесс создания класса до его существования — подменить неймспейс через __prepare__, переопределить __call__ так, чтобы C() вообще не создавал новый объект, или контролировать сам type. Всё это живёт в enum, abc, Django ORM и pydantic — то есть в библиотеках, а не в прикладном коде.
type — сам себе тип? Разве это не порочный круг?Круг есть, но он замкнут в C, а не в Python — и никакой рекурсии при исполнении не происходит.
print(type(1), type(int), type(type))
# → <class 'int'> <class 'type'> <class 'type'>
print(type.__class__ is type) # → True
При старте интерпретатора структуры PyType_Type и PyBaseObject_Type создаются как статические C-структуры, и поле ob_type у type просто заполняется указателем на саму себя. Это присваивание, а не вычисление — обходить цепочку никто не будет.
Вторая половина круга такая же: object — базовый класс всего, а type — его подкласс; при этом type является типом object. Оба утверждения верны одновременно и оба зафиксированы вручную при инициализации.
print(type.__mro__) # → (type, object)
print(isinstance(object, type), issubclass(type, object)) # → True True
Практический смысл, а не курьёз: из «класс — тоже объект, и у него есть тип» следует, что тип класса можно заменить — это и есть метакласс. Не отдельная сущность языка, а обычное применение R1 к самим классам. Если бы type не был объектом, метаклассов не существовало бы, но пришлось бы вводить отдельные правила для классов — то есть второе правило вместо одного.
__init_subclass__ или декоратор класса — что выбрать?Разница в одном: наследуется ли поведение.
__init_subclass__ срабатывает автоматически на каждом подклассе, включая те, что напишут потом и в другом месте. Декоратор применяется ровно там, где его написали, и на подклассы не распространяется.
def tag(cls):
cls.tagged = True
return cls
@tag
class Base: pass
class Child(Base): pass
print(Base.tagged, Child.tagged) # → True True (унаследовали атрибут, но не обработку)
class Base2:
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
cls.registered = True
class Child2(Base2): pass
print(hasattr(Base2, "registered"), Child2.registered) # → False True
Обрати внимание на вторую строку: __init_subclass__ не вызывается для самого базового класса — только для потомков.
Правило выбора. Нужно, чтобы каждый наследник автоматически попал в реестр или прошёл проверку, — __init_subclass__. Нужно однократное преобразование конкретного класса (dataclass, functools.total_ordering, регистрация одного обработчика) — декоратор: он виден в месте применения и ничего не навязывает потомкам.
И третий вариант, который часто лучше обоих: явная регистрация вызовом. register(Csv) строкой ниже — скучно, зато находится поиском по коду и не удивляет.
type, который является собственным типом, чтобы цепочка не уходила в бесконечность. Инструкция class — синтаксис для вызова type(имя, базы, словарь), поэтому класс можно создать руками в любой момент — на этом стоят все библиотеки, генерирующие классы из схемы. Метакласс даёт два разных крючка: его __new__ срабатывает при создании класса, его __call__ — при создании экземпляра, и путать их означает получать необъяснимое поведение. Но в прикладном коде метакласс почти всегда избыточен: __init_subclass__ и __set_name__, появившиеся в 3.6, закрывают реестры и поля моделей, наследуются и не конфликтуют — тогда как метакласс у класса ровно один, и это заявка на монополию.C++ решает ту же задачу — «сгенерировать код по описанию» — шаблонами и CRTP, на этапе компиляции. Регистрация подклассов через CRTP выглядит почти как __init_subclass__, только разворачивается компилятором и ничего не стоит в рантайме. Цена — ошибка в шаблоне даёт нечитаемую диагностику, а сгенерированный код нельзя посмотреть; в Python класс можно распечатать и потрогать, но платишь исполнением при импорте.
Java пошла третьим путём: аннотации плюс обработка на этапе сборки или рефлексия в рантайме. Отсюда генераторы кода в сборке — то, чего Python не нужно, потому что «сборка» и «исполнение» у него один и тот же момент.
Три ответа на вопрос «когда решать структуру класса»: при компиляции, при сборке, при импорте. Python выбрал последнее и получил гибкость ценой того, что ошибки объявления видны только при запуске.
Добавили валидацию структуры через метакласс. Модуль перестал импортироваться целиком.
class Validated(type):
def __new__(mcls, name, bases, ns):
assert "schema" in ns, f"{name} без schema"
return super().__new__(mcls, name, bases, ns)
class Model(SomeABC, metaclass=Validated): # SomeABC наследует ABC
schema = {}
TypeError: metaclass conflict: the metaclass of a derived class
must be a (non-strict) subclass of the metaclasses of all its bases
Ревью пропускает, потому что метакласс написан правильно и в изоляции работает. Ломается стык: ABC тянет за собой ABCMeta, а метакласс у класса может быть только один. Отдельно неприятно, что падает это при импорте, то есть роняет весь модуль, а не отдельный тест.
Формальное лечение — унаследовать: class Validated(ABCMeta). Но правильный вывод другой: проверка структуры не требует метакласса вообще.
class Model(SomeABC):
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
assert hasattr(cls, "schema"), f"{cls.__name__} без schema"
Крючок наследуется, ни с чем не конфликтует и работает с любым чужим метаклассом. Правило, выводимое из следствия 4: метакласс — монополия, и брать её стоит, только когда без неё нельзя.
__init_subclass__ вместо метакласса: наследуется, не конфликтует, читается без подготовкиКлассическая задача «собрать все подклассы в реестр» — и три способа её решить, из которых обычно выбирают худший.
class Handler:
registry = {}
def __init_subclass__(cls, *, event=None, **kw):
super().__init_subclass__(**kw)
if event:
Handler.registry[event] = cls
class OnPush(Handler, event="push"): pass
class OnTag(Handler, event="tag"): pass
Обрати внимание на именованный аргумент прямо в списке баз: class OnPush(Handler, event="push"). Это не трюк — class принимает произвольные ключевые аргументы и передаёт их в __init_subclass__. Метакласс для такого не нужен.
| способ | цена |
|---|---|
| декоратор класса | проще всего, но надо не забыть повесить |
__init_subclass__ | срабатывает сам, наследуется, не конфликтует |
| метакласс | то же самое плюс монополия и конфликты с чужими базами |
Когда метакласс всё-таки нужен: когда надо изменить сам процесс создания класса — подменить базы, переписать словарь имён до создания типа, перехватить создание экземпляров через __call__. Всё остальное закрывается левыми двумя строчками таблицы.
Прогони четыре фрагмента, сверяясь с предсказанием. Затем:
class Meta(type):
def __new__(mcls, n, b, ns): print("new класса"); return super().__new__(mcls, n, b, ns)
def __call__(cls, *a): print("call"); return super().__call__(*a)
class C(metaclass=Meta): pass
C(); C()
# Сколько раз напечаталось каждое и почему?
class D:
x = Field()
def __init_subclass__(cls): print("init_subclass")
class E(D): x = Field()
# В каком порядке сработают __set_name__ и __init_subclass__?
import enum
type(enum.Enum)
# Почему у Enum метакласс, а не декоратор?
# Без кода: почему __init_subclass__ не вызывается
# для самого класса, где он определён?
Первый вопрос — ключевой: он разводит два момента из следствия 2 буквально по счётчику вызовов.
Lib/enum.py, EnumType. Здесь метакласс действительно необходим: он превращает обычные имена в теле класса в объекты-члены, запрещает переопределение членов и переопределяет __call__, чтобы Color(1) искало существующий член, а не создавало новый. Обе точки из следствия 2 задействованы одновременно — редкий и честный случай.
Lib/abc.py, ABCMeta. Считает абстрактные методы при создании класса и подменяет __instancecheck__, чтобы работала регистрация виртуальных подклассов. Заметь _abc_registry на слабых ссылках — механизм из урока 09 в живом виде.
pydantic. Собирает поля модели из аннотаций в момент объявления и строит валидатор. Посмотри, как в версии 2 значительная часть работы уехала в __init_subclass__ и в скомпилированное ядро — направление движения то же самое, что у стандартной библиотеки: из метакласса в более лёгкие крючки.
Из L1 ты знаешь про два момента вмешательства. Здесь — точная последовательность.
Порядок при исполнении инструкции class C(B1, B2, metaclass=M, **kw):
metaclass= либо наиболее производный среди метаклассов баз. Если такого нет — конфликт из фрагмента D.M.__prepare__(name, bases, **kw) — возвращает словарь, в котором будет исполняться тело.M(name, bases, ns, **kw) — то есть M.__call__ метакласса метакласса, а он дёргает M.__new__ и M.__init__.type.__new__: вычисляется MRO, заполняются слоты типа, затем вызываются __set_name__ у всех объектов в словаре, у которых он есть.__init_subclass__ ближайшего предка — уже над готовым классом.Отсюда ответ на вопрос из раздела «Проверь»: __init_subclass__ не срабатывает для класса, где определён, потому что ищется он на предках, а в момент создания самого класса его там ещё нет.
Практическое следствие для отладки: если крючок не сработал, вопрос всегда «на каком шаге», и шагов ровно шесть. type(C) покажет метакласс, C.__mro__ — где искался __init_subclass__, а print внутри __prepare__ подтвердит, что метакласс вообще выбран тот, который ожидался.
Тег v3.11.15.
Objects/typeobject.c, type_new — создание класса целиком: выбор метакласса, MRO, обход словаря с вызовом __set_name__, затем __init_subclass__. Вся последовательность из L2 читается прямо оттуда.type_call — то, что делает C(): вызвать __new__, проверить тип результата, вызвать __init__. Следствие 2 в виде тридцати строк.Python/ceval.c, инструкция LOAD_BUILD_CLASS и функция builtin___build_class__ в Python/bltinmodule.c — как компилятор превращает class в вызов.__init_subclass__ и __set_name__; в мотивации прямо написано, какие применения метаклассов они заменяют.__prepare__obj.x насквозь — __set_name__ вызывается на дескрипторах оттудаdataclass генерирует __init__ через exec — это декоратор, а не метакласс; в L08__init_subclass__ и __set_name__ — хватит разбора выше; идти за точной сигнатурой.