关于python:谁调用了元类

Who calls the metaclass

这实际上源于这里的讨论。

短版

1
2
3
4
5
def meta(name, bases, class_dict)
    return type(name, bases, class_dict)

class Klass(object):
    __metaclass__ = meta

执行Klass类声明时调用meta()

(python internal)代码的哪个部分实际调用meta()

长版

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

我已经对cpython做了一些挖掘(见下文),但我真的希望有更接近一个明确的答案。

选项1:直接调用

元类调用硬连接到类分析中。如果是这样,有证据证明吗?

方案二:由type.__new__()调用

type_call()中的代码调用type_new(),后者依次调用_PyType_CalculateMetaclass()。这表明,当试图找出从type()返回的值时,元类解析实际上是在调用type()期间完成的。

这与"类"是"返回对象的可调用"的概念是一致的。

选项3:不同的东西

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

Some example cases that we came up with in chat:

例1:

1
2
3
4
5
6
7
class Meta(type):
    pass

class A:
    __metaclass__ = Meta

A.__class__ == Meta

这就是Meta.__new__()的回报,所以这似乎是合法的。元类称自己为A.__class__

例2:

1
2
3
4
5
6
7
8
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关于元类的非常简洁的入门:http://blog.ionelmc.ro/2015/02/09/Understanding python metaclasses/。还有一些非常好的图表、引用,也突出了Python2和3之间的区别。

编辑3:下面有一个关于python 3的好答案。python 3使用__build_class__创建类对象。但是,代码路径在Python2中是不同的。


你可以相对容易地找到答案。首先,让我们找到构建类的操作码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> 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镜像上很容易完成)。

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

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

因此,答案是元类由负责执行build_class操作码的函数解析和调用。


在python 3中,在__build_class__内置函数(用于处理class语句)的代码中调用元类。这个函数在python 3中是新的,而python 2中等价的c函数build_class在python级别上没有公开。不过,您可以在python/ceval.c中找到源代码。

无论如何,下面是在python 3 __build_class__实现中对元类对象的相关调用:

1
cls = PyEval_CallObjectWithKeywords(meta, margs, mkw);

变量meta是元类(type或从参数或基类类型中找到的另一个元类)。margs是一个包含位置参数(name, bases, dct)的元组,mkw是一个包含元类关键字参数的字典(只有python 3)。

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

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


在执行类定义时,解释器将元类"实例化"。