这实际上源于这里的讨论。
短版
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中是不同的。
- stackoverflow.com/questions/100003/…
- fwiw,最初的讨论是关于SomeClass()是否向SomeClass.__class__.__call__()作出决议(最终将委托给type.__new__()),或者如果iit向type.__call__()作出决议(将委托给SomeClass.__class__.__call__)。
- @我认为我们现在已经解决了问题,这是前者。不过,还有一些谜语还没解开。)
- @我好像错过了什么…想启发我吗?
- @brunodesthuilliers stackoverflow.com/questions/22083327/…似乎建议您可以通过覆盖直接调用的__class__.__call__来做非常邪恶的事情。这两条路都叫type.__new__(),所以这里的问题仍然没有答案。
- @现在显然是这样;)
你可以相对容易地找到答案。首先,让我们找到构建类的操作码。
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示例-OP询问了它在python 2.7.x中的工作原理。
- 代码来自github.com/python-git/python,它用于2.7。仔细看build_class函数,你能看到它肯定是2.7,它实际上是在寻找__metaclass__这个名字,在3.x中被忽略了。
- 是的,这是python 2的答案。我正要更新我的答案,说大致相同的话。我还是会更新我的答案,但你也会从我这里得到+1!
- 所以它确实是选项1,因为解析器显式地为直接调用__metaclass__.__call__()的类生成生成生成代码。需要弄清楚_PyType_CalculateMetaclass()实际上做了什么。
- @沙丘还好,谢谢,问题解决了。
- 附带说明:您可以直接链接github搜索:github.com/python git/python/&hellip;
- @你知道的越多。谢谢。_PyType_CalculateMetaclass主要用于检测元类冲突。也就是说,一个类只能有一个元类。如果一个类或它的基有不严格的子类型的元类,那么可能会有奇怪的问题。另外,如果当前类声明它是元类作为MetaParent,并且其中一个基使用MetaChild,那么将使用MetaChild来保持元类层次结构的一致性。
- @沙丘:谢谢你投入的所有努力。我想到目前为止我们都经历了一个非常有教育意义的一天。另外:感谢您提醒我,如果需要,我可以随意提取字节码。
在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); |
在执行类定义时,解释器将元类"实例化"。