关于python:python3元类的调用顺序

The call order of python3 metaclass

当我试图理解metaclass创建类实例的顺序时,我很困惑。根据此图(来源),enter image description here

我输入以下代码来验证它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Meta(type):
    def __call__(self):
        print("Meta __call__")
        super(Meta, self).__call__()

    def __new__(mcs, name, bases, attrs, **kwargs):
        print("Meta __new__")
        return super().__new__(mcs, name, bases, kwargs)

    def __prepare__(msc, name, **kwargs):
        print("Meta __prepare__")
        return {}

class SubMeta(Meta):
    def __call__(self):
        print("SubMeta __call__!")
        super().__call__()

    def __new__(mcs, name, bases, attrs, **kwargs):
        print("SubMeta __new__")
        return super().__new__(mcs, name, bases, kwargs)

    def __prepare__(msc, name, **kwargs):
        print("SubMeta __prepare__")
        return Meta.__prepare__(name, kwargs)

class B(metaclass = SubMeta):
    pass

b = B()

然而,结果似乎不像下面这样。

1
2
3
4
5
6
SubMeta __prepare__
Meta __prepare__
SubMeta __new__
Meta __new__
SubMeta __call__!
Meta __call__

如有任何帮助,我们将不胜感激。


发现的诡计

更新2:根据行为,下面调用M0.__call__的事实必须是cpython源(Python/bltinmodule.cbuiltin__build_class中此行的副作用。好的。

为了定义一个具有元类的类,我们通常称元类的__prepare____new____init__。这在下面的例子中创建了一个类,Meta,它是可调用的,但是它的内部PyFunction_GET_CODE槽不是指向它自己的__call__,而是指向它的元类的__call__。因此,如果我们调用Meta()(元类对象),我们调用M0.__call__:好的。

1
2
3
print("call Meta")
print("Meta returns:", Meta('name', (), {}))
print("finished calling Meta")

生产:好的。

1
2
3
4
5
6
call Meta
M0 __call__: mmcls=<class '__main__.Meta'>, args=('name', (), {}), kwargs={}
Meta __new__: mcs=<class '__main__.Meta'>, name='name', bases=(), attrs={}, kwargs={}
Meta __init__: mcs=<class '__main__.name'>, name='name', bases=(), attrs={}, kwargs={}
Meta returns: <class '__main__.name'>
finished calling Meta

换句话说,我们看到Meta的行为类似于type,但它(相当神奇,没有很好的记录)调用M0.__call__。毫无疑问,这是因为在类的类型中查找__call__,而不是在类的实例中查找(实际上除了我们正在创建的实例之外,没有其他实例)。这实际上是一般情况:我们在Meta的类型上称为__call__Meta的类型是M0的事实是:好的。

1
print("type(Meta) =", type(Meta))

印刷品:好的。

1
type(Meta) = <class '__main__.M0'>

这就解释了这是从哪里来的。(我仍然认为应该在文档中强调这一点,文档中还应该描述对元类类型的约束,这些约束在Lib/types.py中的_calculate_winner和在objects/typeobject.c中作为c代码在_PyType_CalculateMetaclass中强制执行。)好的。更新了原始答案

我不知道您的图表来自何处,但它是错误的。update:您实际上可以为您的元类创建一个元类;请参阅jsbueno的答案,我已经更新了下面的示例。新的句子/文本是粗体的,除了最后一节描述我对明显缺乏文档的困惑。好的。

现有的元类代码至少有一个错误。最重要的是,它的__prepare__需要是一个类方法。另请参见使用元类的"call"方法而不是"new"方法。PEP 3115。而且,要使用元类,元类需要有自己的元类,而不是基类。好的。

克里斯的回答包含正确的定义。但元类方法参数和类方法参数之间存在一些不幸的不对称性,我将在下面说明。好的。

还有一件事可能会有所帮助:请注意,在创建类EDOCX1的任何实例(25)之前调用元类__prepare__方法:当定义class B本身时调用它。为了证明这一点,这里有一个正确的元类和类。我还增加了一些插画。我还添加了一个元类,基于jsbueno的答案。我在上面找不到正式的python文档,但我已经更新了下面的输出。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class M0(type):
    def __call__(mmcls, *args, **kwargs):
        print("M0 __call__: mmcls={!r},"
             "args={!r}, kwargs={!r}".format(mmcls, args, kwargs))
        return super().__call__(*args, **kwargs)

class Meta(type, metaclass=M0):
    def __call__(cls, *args, **kwargs):
        print("Meta __call__: cls={!r},"
             "args={!r}, kwargs={!r}".format(cls, args, kwargs))
        return super().__call__(*args, **kwargs)

    def __new__(mcs, name, bases, attrs, **kwargs):
        print("Meta __new__: mcs={!r}, name={!r}, bases={!r},"
             "attrs={!r}, kwargs={!r}".format(mcs, name, bases, attrs, kwargs))
        return super().__new__(mcs, name, bases, attrs)

    def __init__(mcs, name, bases, attrs, **kwargs):
        print("Meta __init__: mcs={!r}, name={!r}, bases={!r},"
             "attrs={!r}, kwargs={!r}".format(mcs, name, bases, attrs, kwargs))
        super().__init__(name, bases, attrs, **kwargs)

    @classmethod
    def __prepare__(cls, name, bases, **kwargs):
        print("Meta __prepare__: name={!r},"
             "bases={!r}, kwargs={!r}".format(name, bases, kwargs))
        return {}

print("about to create class A")
class A(metaclass=Meta): pass
print("finished creating class A")

print("about to create class B")

class B(A, metaclass=Meta, foo=3):
    @staticmethod
    def __new__(cls, *args, **kwargs):
        print("B __new__: cls={!r},"
             "args={!r}, kwargs={!r}".format(cls, args, kwargs))
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print("B __init__: args={!r}, kwargs={!r},".format(args, kwargs))

print("finished creating class B")

print("about to create instance b = B()")
b = B('hello', bar=7)
print("finished creating instance b")

现在,让我们观察一下当我运行这个程序时会发生什么,并将每个部分分开:好的。

1
2
3
4
5
6
7
$ python3.6 meta.py
about to create class A
Meta __prepare__: name='A', bases=(), kwargs={}
M0 __call__: mmcls=<class '__main__.Meta'>, args=('A', (), {'__module__': '__main__', '__qualname__': 'A'}), kwargs={}
Meta __new__: mcs=<class '__main__.Meta'>, name='A', bases=(), attrs={'__module__': '__main__', '__qualname__': 'A'}, kwargs={}
Meta __init__: mcs=<class '__main__.A'>, name='A', bases=(), attrs={'__module__': '__main__', '__qualname__': 'A'}, kwargs={}
finished creating class A

为了创建类A,python首先调用元类的__prepare__,将类的名称(A)、基类列表(空元组,它被称为列表,但实际上是元组)和任何关键字参数(无)传递给它。正如pep 3115所指出的,元类需要返回一个字典或类似于dict的对象;这个类只返回一个空字典,所以我们在这里很好。好的。

(我不在这里打印cls本身,但如果你这样做,你会发现它只是。)好的。

接下来,在从__prepare__获得字典之后,python首先调用meta-meta-__call__,即M0.__call__,将整个参数集作为args元组传递。然后,它在__prepare__提供的字典中填充该类的所有属性,将其作为attrs传递给元类__new____init__。如果打印从__prepare__返回的字典的id并传递给__new____init__的话,您会看到它们都匹配。好的。

由于类A没有方法或数据成员,因此我们在这里只看到了神奇的__module____qualname__属性。我们也没有看到关键字参数,所以现在让我们继续创建类B:好的。

1
2
3
4
5
6
about to create class B
Meta __prepare__: name='B', bases=(<class '__main__.A'>,), kwargs={'foo': 3}
M0 __call__: mmcls=<class '__main__.Meta'>, args=('B', (<class '__main__.A'>,), {'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0a58>, '__init__': <function B.__init__ at 0x800ad2840>, '__classcell__': <cell at 0x800a749d8: empty>}), kwargs={'foo': 3}
Meta __new__: mcs=<class '__main__.Meta'>, name='B', bases=(<class '__main__.A'>,), attrs={'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0940>, '__init__': <function B.__init__ at 0x800ad27b8>, '__classcell__': <cell at 0x800a745b8: empty>}, kwargs={'foo': 3}
Meta __init__: mcs=<class '__main__.B'>, name='B', bases=(<class '__main__.A'>,), attrs={'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0940>, '__init__': <function B.__init__ at 0x800ad27b8>, '__classcell__': <cell at 0x800a745b8: Meta object at 0x802047018>}, kwargs={'foo': 3}
finished creating class B

这个比较有趣。现在我们有一个基类,即__main__.A。类B还定义了几种方法(__new____init__,我们在传递给元类__new____init__方法的attrs字典中看到它们(记住,这些方法只是元类__prepare__返回的现在填充的字典)。和以前一样,传递通过元元类M0.__call__进行。我们还可以看到一个关键字参数,{'foo': 3}。在属性字典中,我们还可以观察到神奇的__classcell__条目:请参见为python 3.6元类提供classcell示例,以获得关于这是什么的简短描述,但实际上,er,super short,它是为了使super()工作。好的。

关键字参数传递给所有三个元类方法,加上元类的方法。(我不太清楚为什么。请注意,在任何元类方法中修改字典都不会对其他方法产生影响,因为它每次都是原始关键字参数的副本。但是,我们可以在元类中对其进行修改:将kwargs.pop('foo', None)添加到M0.__call__中观察到这一点。)好的。

现在我们有了类AB,我们就可以开始创建类B的实际实例了。现在我们看到元类的__call__被调用(而不是元类的):好的。

1
2
about to create instance b = B()
Meta __call__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}

有可能改变通过的argskwargs,但我没有;上面的示例代码最终会调用type.__call__(cls, *args, **kwargs)(通过super().__call__的魔力)。依次称为B.__new__B.__init__:好的。

1
2
3
B __new__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}
B __init__: args=('hello',), kwargs={'bar': 7},
finished creating instance b

它完成了类B的新实例的实现,然后我们将其绑定到名称B上。好的。

注意,B.__new__说:好的。

1
return super().__new__(cls)

所以我们调用object.__new__来创建实例,这或多或少是所有版本python的一个要求;当您返回一个单例实例时,您只能"欺骗"(理想情况下是不可修改的实例)。就是type.__call__在这个对象上调用B.__init__,传递我们传递的参数和关键字参数。如果我们将Meta__call__替换为:好的。

1
2
3
4
    def __call__(cls, *args, **kwargs):
        print("Meta __call__: cls={!r},"
             "args={!r}, kwargs={!r}".format(cls, args, kwargs))
        return object.__new__(cls)

我们将看到,B.__new__B.__init__从未被称为:好的。

1
2
3
about to create instance b = B()
Meta __call__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}
finished creating instance b

实际上,这将创建一个无用/未初始化的实例B。因此,元类__call__方法调用基础类的__init__是至关重要的,通常通过super().__call__调用type.__call__。如果基础类有一个__new__,那么元类应该首先调用它,通常通过调用type.__call__再次调用它。好的。旁注:文件说明了什么

引用第3.3.3.6节:好的。

Once the class namespace has been populated by executing the class body, the class object is created by calling metaclass(name, bases, namespace, **kwds) (the additional keywords passed here are the same as those passed to __prepare__).

Ok.

这就解释了在创建B类作为B类的实例时对Meta.__call__的调用,而不是python在创建A类和B类之前首先调用M0.__call__的事实。好的。

下一段提到了__classcell__条目;之后的一段描述了__set_name____init_subclass__钩子的使用。这里没有任何内容告诉我们,在这一点上,python如何或为什么调用M0.__call__。好的。

在前面的第3.3.3.3到3.3.3.5节中,文档描述了确定元类、准备类名称空间和执行类主体的过程。这是应该描述元类操作的地方,但不是。好的。

几个附加的部分描述了一些附加的约束。一个重要的例子是3.3.10,它讨论了如何通过对象类型找到特殊方法,绕过常规成员属性查找,甚至(有时)绕过元类getattribute,说:好的。

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

Ok.

更新2:这确实是技巧的秘密:通过类型的类型找到特殊的__call__方法。如果元类有元类,元类提供__call__槽;否则元类的类型为type,因此__call__槽为type.__call__。好的。好啊。


尽管@torek的回答很冗长,而且还有很多关于类创建的其他细节,但您对这个问题的总结基本上是正确的。

您的代码中唯一的错误,使您感到困惑的是,您称之为Meta的te类必须是来自SubMeta的元类,而不是它的父类。

只需将SubMeta声明更改为:

1
2
class SubMeta(type, metaclass=Meta):
    ...

(它也不需要从"meta"继承——它只能从type派生。另外,考虑到对type.__call__进行定制,这在创建类的实例(即调用SubMeta.__call__时)和类本身(调用Meta.__call__时)时都很有用)

这是我刚刚在终端输入的另一个较短的示例。抱歉,命名不一致,而且不太完整,但它显示了要点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class M(type):
    def __call__(mmcls, *args, **kwargs):
        print("M's call", args, kwargs)
        return super().__call__(*args, **kwargs)

class MM(type, metaclass=M):
    def __prepare__(cls, *args, **kw):
        print("MM Prepare")
        return {}
    def __new__(mcls, *args, **kw):
        print("MM __new__")
        return super().__new__(mcls, *args, **kw)

class klass(metaclass=MM):
    pass

在处理klass主体后,python输出为:

1
2
3
MM Prepare
M's call ('klass', (), {'__module__': '__main__', '__qualname__': 'klass'}) {}
MM __new__

此外

如您所见,使用元元类,可以自定义元类__init____new__的调用顺序和参数,但仍有一些步骤无法从纯python代码自定义,需要对api的本机调用(以及可能的原始对象结构操作),即:

  • 不能控制对__prepare__的呼叫
  • 无法控制对所创建类的__init_subclass__的调用
  • 可以控制何时调用描述符的__set_name__

最后两项发生在meta meta的__call__返回之后,以及在恢复流到类模块所在的模块之前。