【问题标题】:Who calls the metaclass谁调用元类
【发布时间】:2015-08-31 09:50:31
【问题描述】:

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

短版

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

class Klass(object):
    __metaclass__ = meta

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

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

加长版

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

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

选项 1:直接调用

元类调用被硬连线到类解析中。如果是这样,有任何证据吗?

选项2:由type.__new__()调用

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

这符合“类”是“返回对象的可调用对象”的概念。

选项 3:不同的东西

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

我们在聊天中想出的一些例子:

示例 1:

class Meta(type):
    pass

class A:
    __metaclass__ = Meta

A.__class__ == Meta

这是Meta.__new__() 返回的内容,所以这似乎是合法的。元类将自己设置为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 非常简洁的元类入门:http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/。还有一些非常漂亮的图表、参考资料,也突出了 python 2 和 3 之间的区别。

编辑 3:对于 Python 3,下面有一个很好的答案。Python 3 使用 __build_class__ 创建一个类对象。 Python 2 中的代码路径——然而——不同。

【问题讨论】:

  • FWIW,最初的讨论是关于 SomeClass() 是否解析为 SomeClass.__class__.__call__()(最终将委托给 type.__new__(),或者 iit 解析为 type.__call__()(这将委托给 @987654351 @)。
  • @brunodesthuilliers 我认为我们现在已经解决了这个问题,而且是前者。不过,仍有一些谜团:)
  • @dhke 好像我遗漏了什么... 愿意启发我吗?
  • @dhke 现在显然是 ;)

标签: python python-2.7 metaclass


【解决方案1】:

元类在执行类定义时由解释器“实例化”。

【讨论】:

    【解决方案2】:

    在 Python 3 中,元类在 __build_class__ 内置函数的代码中被调用(调用它来处理 class 语句)。此函数是 Python 3 中的新功能,Python 2 中等效的 C 函数 build_class 并未在 Python 级别公开公开。但是,您可以在 python/ceval.c 中找到源代码

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

    cls = PyEval_CallObjectWithKeywords(meta, margs, mkw);
    

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      所以答案是元类被负责执行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()到底做了什么。
      • @dhke 你知道的越多。谢谢。 _PyType_CalculateMetaclass 主要用于检测元类冲突。也就是说,一个类只能有一个元类。如果一个类或其基类的元类不是严格的子类型,那么可能会出现奇怪的问题。此外,如果当前类将其元类声明为 MetaParent 并且其基类之一使用 MetaChild,则将改用 MetaChild 以保持元类层次结构一致。
      猜你喜欢
      • 2013-05-06
      • 2019-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-14
      相关资源
      最近更新 更多