python3元类的调用顺序

The call order of python3 metaclass

我在试图理解 metaclass 创建 class 实例的顺序时感到困惑。根据这个 (source),

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

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()

然而,结果似乎不是这样的。

SubMeta __prepare__
Meta __prepare__
SubMeta __new__
Meta __new__
SubMeta __call__!
Meta __call__

任何帮助将不胜感激。

诡计,鉴定

更新 2: 根据行为,下面调用 M0.__call__ 的事实 必须 this line in builtin__build_class 在 CPython 来源 (Python/bltinmodule.c).

为了定义具有元class的class,我们调用元class的__prepare____new____init__ 和往常一样。这将创建一个 class——在下面的示例中,Meta——它是可调用的,但它的内部 PyFunction_GET_CODE 插槽不指向 它自己的 __call__ 而不是它的 metaclass 的 __call__。因此,如果我们调用 Meta()(metaclass 对象),我们将调用 M0.__call__:

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

产生:

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__。这无疑是因为在 class 的类型中查找 __call__,而不是在 class 的实例中查找(实际上除了我们创造)。这实际上是一般情况:它脱离了我们在 Metatype 上调用 __call__ 以及 Meta 的类型这一事实] 是 M0:

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

打印:

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

这解释了这是从哪里来的。 (我仍然认为应该在文档中强调这一点,它还应该描述对 metaclass 类型的限制——这些在 _calculate_winner in Lib/types.py and, as C code, in _PyType_CalculateMetaclass in Objects/typeobject.c 中强制执行。)

更新原始答案

我不知道你的图是从哪里来的,但它是错误的。 更新: 事实上你可以有一个元class 为您的元class;请参阅 ,我已经更新了下面的示例。新句子/文本以 粗体显示, 除了最后一节描述了我对明显缺乏文档的困惑。

您现有的元class 代码至少有一个错误。最重要的是,它的 __prepare__ 需要是 class 方法。另见 Using the __call__ method of a metaclass instead of __new__? and PEP 3115并且,要使用 meta-meta-class,您的 metaclass 需要有自己的 metaclass,不是一个碱基class.

Chris's answer 包含正确的定义。但是在 metaclass 方法参数和 class 方法参数之间存在一些不幸的不对称,我将在下面说明。

另一件可能有帮助的事情:请注意在创建 class B 的任何实例之前 调用了 metaclass __prepare__ 方法: 在定义 class B 本身时调用它。为了证明这一点,这里是一个更正的元class-and-class。我还添加了一些插图画家。 我也根据 jsbueno 的回答添加了元元class。我找不到关于此的正式 Python 文档,但我更新了下面的输出。

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")

现在,让我们观察当我 运行 这样做时会发生什么,并将每个部分拆开:

$ 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

要创建 class A 本身,Python 首先调用元 class 的 __prepare__,将 [=255] 的名称传递给它=] (A),基本 classes 的列表(一个空元组——它被称为列表,但实际上是一个元组),以及任何关键字参数 (none)。正如 PEP 3115 所指出的,metaclass 需要 return 字典或 dict 类对象;这个只需要 return 一个空字典,所以我们在这里很好。

(我不在这里打印 cls 本身,但如果你这样做,你会看到它只是 <class '__main__.Meta'>。)

接下来,从 __prepare__ 得到字典后,Python 首先调用元元 __call__,即 M0.__call__,通过整个参数集作为 args 元组。 然后它使用 class 的所有属性填充 __prepare__ 提供的字典,将其作为 attrs 到元 class __new____init__。如果你打印字典 return 的 id__prepare__ 编辑并传递给 __new____init__ 你会看到它们都匹配。

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

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

这个比较有意思。现在我们有一个碱基class,即__main__.A。 Class B 还定义了几种方法(__new____init__),我们在传递给 metaattrs 字典中看到它们 class __new____init__ 方法(请记住,它们只是由元 class 的 __prepare__ 编辑的现在填充的字典 return)。 和以前一样,传递是通过 meta-meta-class M0.__call__. 我们还看到一个关键字参数,{'foo': 3}。在属性字典中,我们还可以观察到神奇的 __classcell__ 条目:请参阅 以获取关于这是什么的简短描述,但是,呃,super-简而言之,它是为了让 super() 工作。

关键字参数被传递给所有三个 metaclass 方法,加上 meta-meta-class 的方法。 (我不太确定为什么。请注意,在任何 metaclass 方法中修改字典不会影响任何其他方法,因为它每次都是原始副本关键字参数。但是,我们可以在meta-meta-class中修改它:将kwargs.pop('foo', None)添加到M0.__call__以观察这个.)

现在我们有了 classes AB,我们可以开始创建 class [=47= 的实际实例的过程].现在我们看到 metaclass 的 __call__ 被调用了(不是 meta-meta-class 的):

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__:

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

这完成了 class B 的新实例的实现,然后我们将其绑定到名称 b.

请注意 B.__new__ 表示:

return super().__new__(cls)

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

    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__ 从未调用过:

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

这实际上会创建一个 useless/uninitialized 实例 b。因此,metaclass __call__ 方法调用底层 class 的 __init__ 很重要,通常是通过 super().__call__ 调用 type.__call__。如果基础 class 有一个 __new__,元 class 应该首先调用它,通常再次调用 type.__call__.

旁注:the documentation 说了什么

引用第 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__).

这解释了在创建 b 作为 class B 的实例时对 Meta.__call__ 的调用,而不是 Python 首先调用 M0.__call__ 在调用 Meta.__new__Meta.__init__ 之前创建 classes AB 自己。

下一段提到了 __classcell__ 条目;之后的一个继续描述 __set_name____init_subclass__ 钩子的使用。这里没有任何内容告诉我们 Python 此时如何或为何调用 M0.__call__

早些时候,在 3.3.3.3 到 3.3.3.5 节中,文档描述了确定 metaclass、准备 class 命名空间和执行 class 主体的过程.这是 meta-metaclass 操作 应该 描述的地方,但不是。

几个附加部分描述了一些附加限制。一个重要的是 3.3.10,它讨论了如何通过对象类型找到特殊方法,绕过常规成员属性查找甚至(有时)metaclass 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).

更新 2:这确实是诀窍的秘密:特殊的 __call__ 方法是通过类型的类型找到的。如果metaclass有metaclass,meta-meta-class提供__call__槽;否则 metaclass 的类型是 type,因此 __call__ 插槽是 type.__call__.

尽管@torek 的回答冗长,有很多 other 关于 class 创作的详细信息,但您对这个问题的总结大部分是正确的。

您的代码中唯一有问题的是,您调用 Meta 的 class 本身必须是 metaclass 来自 SubMeta 而不是其父级。

只需将 Submeta 声明更改为:

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

(它也不需要从 "Meta" 继承 - 它只能从 type 派生。否则,尽管考虑对 type.__call__ 的自定义同时可用于创建 classes 实例(即调用 SubMeta.__call__ 时)和 classes 本身(调用 Meta.__call__))

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

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 输出为:

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

此外

从这里可以看出,使用 meta-meta class 可以自定义 metaclass __init__ 和 [=22= 的调用顺序和参数], 但是 仍然有一些步骤无法从纯 Python 代码自定义,并且需要对 API 的本地调用(可能是原始的对象结构操作)- 即:

  • 无法控制对 __prepare__
  • 的调用
  • 无法控制在创建的 classes
  • 上对 __init_subclass__ 的调用
  • 可以控制何时调用描述符的 __set_name__

最后两项发生在 meta-meta 的 __call__ return 之后,并且在恢复流向 class 模块所在的模块之前。