Итак --- несколько сбивающий с толку вопрос, на который можно ответить и несколько упростить, просто запустив несколько примеров в интерактивном режиме.
Но для начала, когда вы заявляете:
type.__call__(...) in turn run two other methods (a __new__ and a __init__).
Это упрощение того, что происходит.
Когда мы создаем новый класс, например, при разрешении оператора класса class A:
, type.__call__
вызывается нормально. Но этот вызов ищется в классе самого Meta
. То есть метакласс Meta, который по умолчанию равен type
.
Потерпите меня: когда мы говорим об обычном классе E без настраиваемого метакласса, и вы создаете экземпляр, выполняя E()
, Python ищет метод __call__
в классе, экземпляром которого является E
: то есть его метаклассом. Так как это типа, то называется type.__call__
. Это type.__call__
, который вызывает методы __new__
и __init__
, как вы сказали, но не только для метаклассов: он организует эти вызовы в любом экземпляре объекта - точно такой же механизм используется в любом экземпляре объекта в Python: как обычные объекты, так и классы:
In [178]: class MetaMeta(type):
...: def __call__(metacls, *args, **kw):
...: print("Now at the meta-meta class")
...: return super().__call__(*args, **kw)
...:
In [179]: class EmptyMeta(type, metaclass=MetaMeta):
...: def __call__(cls, *args, **kw):
...: print("At the metaclass __call__")
...: return super().__call__(*args, **kw)
...:
...:
...:
In [180]: class A(metaclass=EmptyMeta):
...: pass
...:
Now at the meta-meta class
In [181]: a = A()
At the metaclass __call__
In [182]: class Direct(metaclass=MetaMeta): pass
In [183]: Direct()
Now at the meta-meta class
Out[183]: <__main__.Direct at 0x7fa66bc72c10>
Итак, вкратце: при создании класса A, который является экземпляром Meta, вызывается метод __call__
класса Meta. Это вызовет __init__
и __new__
в метаклассе Meta. Если они не определены, обычный поиск атрибутов вызовет эти методы в суперклассе Meta, который также является типом.
Теперь, переходя к вашему вопросу: когда кто-то наследуется от класса с настраиваемым метаклассом, таким как ваш класс B
, Python берет наиболее производный метакласс в своих суперклассах в качестве своего собственного метакласса, а не type
. Нет необходимости явно объявлять пользовательский метакласс. На практике это то, что делает метакласс необходимым, а не просто декораторами классов: они влияют только на класс, в котором они объявлены, и не действуют на последующие подклассы.
In [184]: class B(A): pass
Now at the meta-meta class
In [185]: B()
At the metaclass __call__
Out[185]: <__main__.B at 0x7fa6682ab3a0>
In [186]: B.__class__
Out[186]: __main__.EmptyMeta
Даже при явном вызове type
вместо оператора class
метакласс производного класса будет метаклассом суперкласса. Обратите внимание, однако, что в этом случае мы жестко кодируем вызов метамета-класса на type.__new__
, а пользовательский метакласс метакласса игнорируется:
In [187]: C = type("C", (A, ), {})
In [188]: C()
At the metaclass __call__
Out[188]: <__main__.C at 0x7fa653cb0d60>
Если вы хотите программно создать класс, который имеет пользовательский мета-метакласс (упаси бог, он понадобится для чего-либо, кроме целей обучения), в модуле types
есть специальный вызов, который делает это:
In [192]: import types
In [193]: D = types.new_class("D", (A,), {})
Now at the meta-meta class
In [194]: D()
At the metaclass __call__
Out[194]: <__main__.D at 0x7fa6682959a0>
И в заключение отметим, что если суперклассы класса имеют расходящиеся метаклассы, Python вообще откажется создавать класс. Это несколько распространено в коде реального мира, когда люди пытаются создать абстрактные классы (которые используют пользовательский метакласс) с базовым классом в какой-либо структуре с ORM, которые обычно также имеют пользовательский метакласс:
In [203]: class Meta1(type): pass
In [204]: class Meta2(type): pass
In [205]: class A(metaclass=Meta1): pass
In [206]: class B(metaclass=Meta2): pass
In [207]: class C(A, B): pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-207-1def53cc27f4> in <module>
----> 1 class C(A, B): pass
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Это можно исправить, создав производный метакласс, который наследуется от метаклассов в обеих ветвях-предках (для этого требуется, чтобы оба метакласса хорошо себя вели, используя super()
вместо вызовов жесткого кодирования type
, но это в случае с хорошо поддерживаемыми и популярными фреймворками):
In [208]: class Meta3(Meta1, Meta2): pass
In [209]: class C(A, B, metaclass=Meta3): pass
In [210]:
person
jsbueno
schedule
02.01.2021