2025, Nov 15 21:03
Как работают метаклассы, __init_subclass__ и декораторы классов в Python
Разбираем порядок создания класса в Python: метаклассы, __init_subclass__ и декораторы классов. Где и когда появляются атрибуты — на примере кода пошагово.
Метаклассы, __init_subclass__ и декораторы классов влияют на то, как «рождается» класс в Python, но задействуются на разных этапах его жизненного цикла. Если вам нужно понять, какие атрибуты окажутся у самого класса, а какие — у его экземпляров, важно чётко представлять порядок вызова этих механизмов.
Пример: один конвейер создания класса и три механизма
Фрагмент ниже объединяет пользовательский метакласс, декоратор класса и __init_subclass__ в небольшой иерархии. Он выводит маркеры, фиксируя точную последовательность событий, и на каждом этапе выставляет атрибуты, чтобы показать, где они в итоге оказываются.
class Orchestrator(type):
def __new__(meta, cls_name, parents, ns):
print(f"Orchestrator.__new__ called for {cls_name}")
ns['meta_attr'] = f"Value from {meta.__name__}"
return super().__new__(meta, cls_name, parents, ns)
def __init__(cls, cls_name, parents, ns):
print(f"Orchestrator.__init__ called for {cls_name}")
super().__init__(cls_name, parents, ns)
cls.meta_init_attr = f"Initialized by {cls.__class__.__name__}"
def label_class(klass):
print(f"Class decorator called for {klass.__name__}")
klass.decorated_attr = "Decorated!"
return klass
@label_class
class RootBase(metaclass=Orchestrator):
def __init_subclass__(subc, **kw):
print(f"RootBase.__init_subclass__ called for {subc.__name__}")
super().__init_subclass__(**kw)
subc.base_subclass_attr = "From MyBase __init_subclass__"
def __init__(self):
print("RootBase instance __init__ called")
self.instance_attr = "Instance created"
print("\nDefining SubType:")
class SubType(RootBase):
def __init_subclass__(subc, **kw):
print(f"SubType.__init_subclass__ called for {subc.__name__}")
super().__init_subclass__(**kw)
subc.derived_subclass_attr = "From MyDerived __init_subclass__"
def __init__(self):
print("SubType instance __init__ called")
super().__init__()
print("\nCreating SubType instance:")
obj = SubType()
print("\nAttributes on SubType class:")
print(f"SubType.meta_attr: {hasattr(SubType, 'meta_attr')}")
print(f"SubType.meta_init_attr: {hasattr(SubType, 'meta_init_attr')}")
print(f"SubType.decorated_attr: {hasattr(SubType, 'decorated_attr')}")
print(f"SubType.base_subclass_attr: {hasattr(SubType, 'base_subclass_attr')}")
print(f"SubType.derived_subclass_attr: {hasattr(SubType, 'derived_subclass_attr')}")
print("\nAttributes on instance:")
print(f"obj.instance_attr: {hasattr(obj, 'instance_attr')}")
Что именно происходит при создании класса
Когда интерпретатор встречает оператор class, он сначала определяет метакласс, обычно исходя из базовых классов. Если у метакласса есть метод __prepare__, он вызывается для создания пространства имён, в котором будет собираться тело класса. Затем в этом пространстве выполняется тело класса: фиксируются методы, атрибуты, а также служебные записи вроде __module__, __qualname__ и любые __annotations__.
Когда пространство имён готово, вызывается __new__ метакласса. Внутри него создаётся сам объект класса, в конечном счёте через делегирование в type.__new__. Важный момент: когда type.__new__ создаёт класс, выполняются все __init_subclass__, определённые в его базовых классах. Именно здесь базовые классы могут настроить только что созданный подкласс. После завершения __new__ запускается __init__ метакласса — для финальной инициализации объекта класса. И лишь после полной сборки класса Python применяет декоратор класса: передаёт ему класс и связывает в окружающей области то, что декоратор вернул, под именем класса.
Позже, при создании экземпляра, срабатывает обычный __init__ класса, где можно назначать атрибуты конкретного экземпляра.
Исправление и результат
Есть важная деталь, если вы пишете код метакласса сами: в __init__ метакласса вызов super не должен передавать класс первым позиционным аргументом. Правильная форма вызова такая:
class Orchestrator(type):
def __init__(cls, cls_name, parents, ns):
# ...
super().__init__(cls_name, parents, ns)
# ...
С этим исправлением процесс идёт именно так, как описано выше. В __new__ метакласса можно внедрить meta_attr в пространство имён до создания класса, затем __init__ метакласса выставит meta_init_attr, а декоратор выполнится последним и добавит decorated_attr. Когда создаётся SubType как подкласс RootBase, во время type.__new__ вызывается RootBase.__init_subclass__, который задаёт base_subclass_attr. Собственный __init_subclass__ у SubType запускается только при создании его потомков, поэтому на сам SubType он не влияет. Наконец, при создании экземпляра срабатывает экземплярный __init__, который устанавливает instance_attr на объекте.
Какие атрибуты где оказываются
У производного класса присутствуют атрибуты meta_attr, meta_init_attr, decorated_attr и base_subclass_attr. У экземпляра — instance_attr. Атрибута derived_subclass_attr нет ни у самого производного класса, ни у его экземпляра, потому что __init_subclass__, который его устанавливает, принадлежит этому производному классу и срабатывает лишь при создании его потомков. И помните: атрибуты класса видны из экземпляров через обычный механизм поиска атрибутов, даже если физически хранятся на классе.
Почему это важно
В повседневной работе редко требуется держать в голове всю последовательность. Важно знать две вещи: декораторы классов применяются в самом конце создания класса, а __init__ выполняется при создании экземпляров. И декораторы классов, и __init_subclass__ позволяют настраивать создание классов без написания метакласса, и во многих случаях этих двух механизмов вполне достаточно.
Напоследок
Держите в голове простую схему: метакласс конструирует класс; базовые классы получают возможность настроить новые подклассы через __init_subclass__ во время работы type.__new__; затем __init__ метакласса выполняет финальную настройку; и только после этого запускается декоратор класса. Если нужно понять, откуда взялся атрибут, сопоставьте его с этапом, на котором он назначается. Для большинства задач используйте декоратор или __init_subclass__, а метакласс оставляйте для действительно необходимых случаев.