子类的元类是如何确定的?

How is the metaclass of a subclass determined?

对于下面的代码:

class TestMeta(type):
    def __init__(cls, classname, bases, dict_):
        print(f'In TestMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class Parent(metaclass=TestMeta):
    pass

class Child(Parent, metaclass=type):
    pass

输出为:

In TestMeta, class <class '__main__.Parent'>
In TestMeta, class <class '__main__.Child'>

在我看来,在创建 class Parent 时,TestMeta.__init__ 会 运行,但为什么在创建 [=] 时又会 运行 27=] Child 我把它的 metaclass 改成了 type?继承传递metaclass时metaclass如何确定?

来自 the docs:

The appropriate metaclass for a class definition is determined as follows:

  • if no bases and no explicit metaclass are given, then type() is used;

  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass;

  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used.

The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses.

在你上面的例子中,第三个项目符号成立,因为 type 作为显式元类给出并且有 base-classes.

根据最派生元类的定义,候选者为:TestMetatype。由于TestMeta继承了type,是最派生的元类,确实是Child的元类。

为了证明区别,下面的代码展示了我们如何 "force" Child 得到指定的 metaclass 而不是继承的:

class TestMeta(type):
    def __init__(cls, classname, bases, dict_):
        print(f'In TestMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class OtherMeta(TestMeta):
    def __init__(cls, classname, bases, dict_):
        print(f'In OtherMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class Parent(metaclass=TestMeta):
    pass

class Child(Parent, metaclass=OtherMeta):
    pass

输出将是:

In TestMeta, class <class '__main__.Parent'>
In OtherMeta, class <class '__main__.Child'>

再次注意,根据最派生元类的定义,这仅在 OtherMeta 本身是 TestMeta 的子类时有效。去掉这个继承关系会报错:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases