Метаклассы

Класс — объект, значит у него есть тип. Этот тип и есть метакласс. Из одного факта выводятся и Django ORM, и pydantic, и ABC — и понимание, что в девяти случаях из десяти метакласс не нужен.

L1 · 8 минL2 · 10 мин📱 телефонсверено · CPython 3.11, linux x86-64

1Предскажи

Зафиксируй вывод и причину до того, как откроешь ответ.

Фрагмент A
print(type(int))
print(type(type))
X = type("X", (object,), {"v": 1})
print(X, X().v)
<class 'type'>
<class 'type'>
<class '__main__.X'> 1
Класс создан вызовом функции. И тип типа — он сам.
Фрагмент B
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']
Реестр наполнился сам, без метакласса и без декоратора.
Фрагмент C
class Field:
    def __set_name__(self, owner, name):
        print("set_name", owner.__name__, name)

class M:
    f = Field()
set_name M f
Дескриптор узнал своё имя в момент создания класса — хотя ему его никто не передавал.
Фрагмент D
from abc import ABC, abstractmethod
class Base(ABC):
    @abstractmethod
    def go(self): ...

class OtherMeta(type): pass
class Conflict(Base, metaclass=OtherMeta): pass
TypeError: metaclass conflict: the metaclass of a derived
class must be a (non-strict) subclass of the metaclasses
of all its bases
Ошибка при объявлении класса, ещё до всякого использования.

2Механизм

Из урока 06 известно: class — исполняемая инструкция, а собранный словарь имён передаётся конструктору типа. Здесь мы называем этот конструктор по имени.

Класс — объект, значит у него есть тип. Тип класса называется метаклассом. По умолчанию это type.

type с тремя аргументами создаёт класс: имя, кортеж баз, словарь имён. Инструкция class — синтаксис для этого вызова.

Метакласс — класс, наследующий type. Переопределяя его методы, вклиниваешься либо в создание класса, либо в создание экземпляров.

Следствие 1 · рекурсия обрывается на самом type

Фрагмент A. type(int) — это type, потому что int создан им. А type(type) — снова type: цепочка «объект → его тип» замыкается на себя, иначе она была бы бесконечной. Это единственное место в языке, где такое замыкание есть, и сделано оно ровно чтобы модель осталась конечной.

И раз класс создаётся вызовом, его можно создать вручную в любой момент — что и делают все библиотеки, генерирующие классы из схемы, конфига или таблицы БД.

Следствие 2 · две разные точки вмешательства

Метакласс даёт два принципиально разных крючка, и их регулярно путают.

__new__ и __init__ метакласса вызываются при создании класса — один раз, когда исполняется class. Сюда вешают проверку структуры, регистрацию, преобразование объявленных полей.

__call__ метакласса вызывается при создании экземпляра: именно он потом дёргает __new__ и __init__ самого класса. Отсюда классический синглтон — перехватить __call__ и вернуть один и тот же объект.

Ошибка «положил логику не в тот метод» даёт код, который работает один раз или, наоборот, на каждом вызове — и выглядит необъяснимо, пока не помнишь, что это разные моменты жизни.

Следствие 3 · три уровня вмешательства, и метакласс — крайний

Фрагменты B и C показывают то, что закрывает большинство реальных задач без метакласса.

__init_subclass__ вызывается на ближайшем предке при создании подкласса — этого достаточно для реестров плагинов, проверки обязательных атрибутов, автоматической регистрации обработчиков. __set_name__ вызывается на каждом дескрипторе в теле класса и сообщает ему имя и владельца — так поля моделей узнают, как их назвали.

Полная лестница по возрастанию цены: декоратор класса (получает готовый класс, проще всего) → __init_subclass__ и __set_name__ (встроенные крючки, наследуются) → метакласс (меняет сам процесс создания). Выбирать надо самое левое, что решает задачу.

Следствие 4 · метаклассы конфликтуют

Фрагмент D. У класса ровно один метакласс, и он обязан быть подклассом метаклассов всех баз. ABC использует ABCMeta; попытка добавить свой метакласс к абстрактному базовому классу даёт конфликт при объявлении.

Лечится наследованием — class MyMeta(ABCMeta), — но сам факт важнее лечения: метакласс — это заявка на монополию. Библиотека, использующая свой метакласс, плохо смешивается с другой такой же. Именно поэтому __init_subclass__ и добавили в 3.6: он наследуется и не конфликтует.

3Границы модели

Где сказанное перестаёт держать

Метакласс наследуется. Он применяется ко всем подклассам автоматически, и отключить это нельзя. Класс, случайно унаследованный от чего-то с метаклассом, получает его поведение целиком.

__prepare__ нужен реже, чем кажется. Он задаёт словарь для тела класса; исторически применялся, чтобы запомнить порядок объявления. С 3.7 обычный словарь упорядочен 🔒, и большинство таких применений отпало — enum тому пример.

Порядок крючков фиксирован: тело исполняется → метакласс создаёт класс → вызываются __set_name__ у дескрипторов → вызывается __init_subclass__ у предка. Логика, зависящая от другого порядка, работать не будет.

4Корень

Корень R2: всё резолвится в рантайме, и класс — обычный объект. Метакласс не отдельная возможность, а прямое следствие: раз класс объект, у него есть тип; раз тип можно заменить, процесс создания можно перехватить.

Механизм появился в 2001 году вместе с объединением типов и классов и десять лет оставался экспертным инструментом с репутацией «глубокой магии». Перелом случился в 3.6: __init_subclass__ и __set_name__ вынули из метаклассов две задачи, ради которых их писали в девяти случаях из десяти. Сегодня метакласс в прикладном коде — почти всегда признак того, что задачу можно решить проще.

5Аналогия

Ближайшая точная аналогия — CRTP в C++: базовый шаблон, параметризованный собственным наследником, который в момент инстанцирования знает про потомка и может его зарегистрировать. Это ровно то, что делает __init_subclass__, только у C++ работу выполняет компилятор, а у Python — интерпретатор при исполнении class.

Разница в цене показательна. CRTP бесплатен в рантайме и дорог в диагностике: ошибка выражается сообщением на две страницы. __init_subclass__ стоит вызова при каждом объявлении класса и отлаживается обычным print. Один и тот же приём, две разные валюты.

6Вопросы пытливого ума

Вопросы, которые возникают сами, если читать внимательно. Ответ — под вопросом.

Если метаклассы такие мощные, почему их почти никто не пишет?

Потому что за десять лет почти всё, ради чего их звали, переехало в более дешёвые механизмы.

Классические задачи метакласса: зарегистрировать подкласс в реестре, проверить, что у него есть нужные атрибуты, дать дескрипторам знать их имя, подставить сгенерированные методы. Сегодня для первых трёх есть __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++ шаблоны и Java-аннотацииГенерация кода компилятором против генерации класса в рантайме

C++ решает ту же задачу — «сгенерировать код по описанию» — шаблонами и CRTP, на этапе компиляции. Регистрация подклассов через CRTP выглядит почти как __init_subclass__, только разворачивается компилятором и ничего не стоит в рантайме. Цена — ошибка в шаблоне даёт нечитаемую диагностику, а сгенерированный код нельзя посмотреть; в Python класс можно распечатать и потрогать, но платишь исполнением при импорте.

Java пошла третьим путём: аннотации плюс обработка на этапе сборки или рефлексия в рантайме. Отсюда генераторы кода в сборке — то, чего Python не нужно, потому что «сборка» и «исполнение» у него один и тот же момент.

Три ответа на вопрос «когда решать структуру класса»: при компиляции, при сборке, при импорте. Python выбрал последнее и получил гибкость ценой того, что ошибки объявления видны только при запуске.

🐛 багСвой метакласс поверх ABCБиблиотека уже использует ABCMeta — класс перестаёт создаваться, ошибка при импорте

Добавили валидацию структуры через метакласс. Модуль перестал импортироваться целиком.

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__. Всё остальное закрывается левыми двумя строчками таблицы.

💻 терминалПроверь в REPLЧетыре фрагмента плюс порядок крючков и синглтон через __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 буквально по счётчику вызовов.

исходникиenum, ABC и pydanticТри библиотеки, где метакласс оправдан — и видно, чем именно

Lib/enum.py, EnumType. Здесь метакласс действительно необходим: он превращает обычные имена в теле класса в объекты-члены, запрещает переопределение членов и переопределяет __call__, чтобы Color(1) искало существующий член, а не создавало новый. Обе точки из следствия 2 задействованы одновременно — редкий и честный случай.

Lib/abc.py, ABCMeta. Считает абстрактные методы при создании класса и подменяет __instancecheck__, чтобы работала регистрация виртуальных подклассов. Заметь _abc_registry на слабых ссылках — механизм из урока 09 в живом виде.

pydantic. Собирает поля модели из аннотаций в момент объявления и строит валидатор. Посмотри, как в версии 2 значительная часть работы уехала в __init_subclass__ и в скомпилированное ядро — направление движения то же самое, что у стандартной библиотеки: из метакласса в более лёгкие крючки.

L2Что именно происходит при исполнении classПолная последовательность вызовов и где сидит каждый крючок

Из L1 ты знаешь про два момента вмешательства. Здесь — точная последовательность.

Порядок при исполнении инструкции class C(B1, B2, metaclass=M, **kw):

  1. Определяется метакласс: явный metaclass= либо наиболее производный среди метаклассов баз. Если такого нет — конфликт из фрагмента D.
  2. Вызывается M.__prepare__(name, bases, **kw) — возвращает словарь, в котором будет исполняться тело.
  3. Тело класса исполняется в этом словаре как обычный код.
  4. Вызывается M(name, bases, ns, **kw) — то есть M.__call__ метакласса метакласса, а он дёргает M.__new__ и M.__init__.
  5. Внутри type.__new__: вычисляется MRO, заполняются слоты типа, затем вызываются __set_name__ у всех объектов в словаре, у которых он есть.
  6. Вызывается __init_subclass__ ближайшего предка — уже над готовым классом.

Отсюда ответ на вопрос из раздела «Проверь»: __init_subclass__ не срабатывает для класса, где определён, потому что ищется он на предках, а в момент создания самого класса его там ещё нет.

Практическое следствие для отладки: если крючок не сработал, вопрос всегда «на каком шаге», и шагов ровно шесть. type(C) покажет метакласс, C.__mro__ — где искался __init_subclass__, а print внутри __prepare__ подтвердит, что метакласс вообще выбран тот, который ожидался.

L3Где это в исходниках CPythontypeobject.c: type_new и type_call, шаг за шагом

Тег 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 в вызов.
  • PEP 487 — введение __init_subclass__ и __set_name__; в мотивации прямо написано, какие применения метаклассов они заменяют.
связиКуда это ведётL06, L04, L05 · контраст с шаблонами C++ и аннотациями Java
корень R2 · Атрибут резолвится в рантайме, неймспейс — dict
← основа L06 · Классы — точка расширения, обещанная там, разобрана здесь
← основа L05 · dict и хешируемость — тело класса это словарь, и его упорядоченность отменила половину применений __prepare__
← основа L04 · obj.x насквозь — __set_name__ вызывается на дескрипторах оттуда
↔ контраст CRTP и шаблоны C++: та же задача, решённая компилятором
↔ контраст Аннотации Java с обработкой при сборке — третий момент принятия решения
→ дальше L08 · Функции и декораторы — декоратор класса как самый лёгкий уровень из следствия 3
чего здесь нет Как dataclass генерирует __init__ через exec — это декоратор, а не метакласс; в L08
источникиЧто почитатьPEP 487 🟥 · создание класса 🟧 · «Python metaclasses» 🟦