谁调用元类

Who calls the metaclass

这实际上源于对 SO 的讨论。

短版

def meta(name, bases, class_dict)
    return type(name, bases, class_dict)

class Klass(object):
    __metaclass__ = meta

meta() 在执行 Klass class 声明时被调用。

(python 内部)代码的哪一部分实际调用了 meta()

长版

当声明 class 时,某些代码必须进行适当的属性检查并查看是否在类型上声明了 __metaclass__。如果存在,它必须使用众所周知的 (class_name, bases, class_dict) 属性对该元 class 执行方法调用。我不太清楚哪个代码负责该调用。

我在 CPython 中做了一些挖掘(见下文),但我真的很想得到更接近确定答案的东西。

方式一:直接调用

metaclass 调用硬连接到 class 解析中。如果是这样,有这方面的证据吗?

选项2:由type.__new__()

调用

type_call() calls type_new() which in turn calls _PyType_CalculateMetaclass() 中的代码。这表明当试图从 type()

中找出 return 的值时,元 class 解析实际上是在调用 type() 期间完成的

这符合 "class" 是“可调用对象return是一个对象”的概念。

选项 3:有所不同

当然,我所有的猜测都可能完全错误。

Some example cases that we came up with in chat:

示例 1:

class Meta(type):
    pass

class A:
    __metaclass__ = Meta

A.__class__ == Meta

这就是 Meta.__new__() return 的内容,因此这似乎是合法的。 metaclass 将自己设为 A.__class__

示例 2:

class Meta(type):
    def __new__(cls, class_name, bases, class_dict):
        return type(class_name, bases, class_dict)

class A(object):
    __metaclass__ = Meta

A.__class__ == type

编辑 2:正确的初始版本,从 type.

正确派生 Meta

看起来还不错,但我不确定这是否符合我的预期。另外:使它像示例 1 中那样运行的规范方法是什么?

编辑 3:使用 type.__new__(...) 似乎按预期工作,这似乎也支持选项 2。

谁能对内部 python 魔法有更深入的了解?

编辑:A 关于 metaclasses 的相当简洁的入门:http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/。还有一些非常好的图表、参考资料,还强调了 python 2 和 3 之间的差异。

编辑 3:Python 3 下面有一个很好的答案。Python 3 使用 __build_class__ 创建一个 class 对象。 Python 2.

中的代码路径 -- 然而-- 不同

当执行 class 定义时,class 元 class 是解释器 "instantiated"。

在Python 3、__build_class__ builtin function (which is called to handle class statements). This function is new in Python 3, and the equivalent C function build_class in Python 2 is not publicly exposed at the Python level. You can however find the source in python/ceval.c

的代码中调用了metaclass

无论如何,这是 Python 3 __build_class__ 实现中对 metaclass 对象的相关调用:

cls = PyEval_CallObjectWithKeywords(meta, margs, mkw);

变量 meta 是元class(type 或从参数或基类型[=35= 中找到的另一个元class ]). margs 是一个带有位置参数 (name, bases, dct) 的元组,mkw 是一个字典,带有 metaclass 的关键字参数(只有 Python 3 的东西)。

Python 2 代码做了类似的事情:

result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
                                      NULL);

您可以相对轻松地找到答案。首先,让我们找到构建 class.

的操作码
>>> def f():
    class A(object):
        __metaclass__ = type

>>> import dis
>>> dis.dis(f)
  2           0 LOAD_CONST               1 ('A')
              3 LOAD_GLOBAL              0 (object)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               2 (<code object A at 0000000001EBDA30, file "<pyshell#3>", line 2>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS         
             19 STORE_FAST               0 (A)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE    

所以操作码是BUILD_CLASS。现在让我们搜索该术语的来源(在 github mirror 上很容易完成)。

您会得到几个结果,但其中最有趣的是 Python/ceval.c,它声明了函数 static PyObject * build_class(PyObject *, PyObject *, PyObject *); 并且有一个 BUILD_CLASS 的 case 语句。搜索文件,您可以找到从第 4430 行开始的 build_class 的函数定义。在第 4456 行,我们找到您要查找的代码:

result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
                      NULL);

所以答案是 metaclass 由负责执行 BUILD_CLASS 操作码的函数解析和调用。