python3元类的调用顺序

时间:2018-11-11 05:10:38

标签: python metaclass

在尝试了解metaclass创建类实例的顺序时,我感到很困惑。根据此图表source),enter image description here

我输入以下代码进行验证。

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 个答案:

答案 0 :(得分:3)

发现了把戏

更新2:根据行为,在{{1中,M0.__call__被称为 下的事实必须是this line的副作用}}(在CPython源代码(builtin__build_class)中。

为了定义具有元类的类,我们照常称呼元类的Python/bltinmodule.c__prepare____new__。这将创建一个可调用的类(在下面的示例中为__init__),但是其内部的Meta插槽不是指向自身的 PyFunction_GET_CODE,而是指向其元类的__call__。因此,如果我们调用__call__(元类对象),则会调用Meta()

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__(实际上,除了我们正在创建的实例之外,没有其他实例)。实际上,这是一般情况:由于我们在__call__类型上调用__call__,而Meta的类型是{ {1}}:

Meta

打印:

M0

解释了它的来源。 (我仍然认为应该在文档中强调这一点,该文档还应该描述对元类类型的约束-这些约束在_calculate_winner in Lib/types.py中强制执行,并且作为C代码在in _PyType_CalculateMetaclass in Objects/typeobject.c中进行强制执行。)

更新后的原始答案

我不知道您的图表来自何处,但这是错误的。 更新:实际上,您可以为您的元类提供一个元类。请参阅jsbueno's answer,并且我更新了以下示例。新的句子/文字为粗体,但最后一节描述的是我显然由于缺乏文档而感到困惑。

您现有的元类代码至少有一个错误。最重要的是,它的print("type(Meta) =", type(Meta)) 必须是一种类方法。另请参见Using the __call__ method of a metaclass instead of __new__?PEP 3115并且,要使用元元类,您的元类需要具有自己的元类,而不是基类。

Chris's answer包含正确的定义。但是,元类方法参数和类方法参数之间存在一些不幸的不对称性,下面将对此进行说明。

另一件事可能会有所帮助:请注意,在创建类type(Meta) = <class '__main__.M0'> 的任何实例之前,先调用元类__prepare__方法:在__prepare__时调用它本身正在定义。为了说明这一点,这里是一个更正的元类和类。我还添加了一些插图画家。 根据jsbueno的回答,我还添加了元元类。我找不到与此相关的正式Python文档,但是我已经更新了下面的输出。

B

现在,让我们观察一下运行此命令时发生的情况,并将每一部分拆开:

class B

要自己创建类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") ,Python首先调用元类的$ 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 ,为其提供类的名称(A),基类列表(一个空的元组,它是称为列表,但实际上是一个元组),以及任何关键字参数(无)。如PEP 3115所述,元类需要返回字典或类似__prepare__的对象;这只是通过返回一个空字典来完成的,所以我们在这里很好。

(我自己不在这里打印A,但如果您这样做,您会看到它只是dict。)

接下来,Python从cls获得字典,首先调用元元<class '__main__.Meta'>,即__prepare__,将整个参数集作为{{ 1}}元组。然后使用该类的所有属性填充__call__提供的字典,将其作为M0.__call__传递给元类args和{{1 }}。如果您打印从__prepare__返回的字典的attrs,并传递给__new____init__,您将看到它们都匹配。

由于类id没有方法或数据成员,因此我们在这里仅看到神奇的__prepare____new__属性。我们也没有看到关键字参数,因此现在让我们继续创建类__init__

A

这个比较有趣。现在我们有了一个基类,即__module__。类__qualname__还定义了几种方法(Babout 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 ),我们在传递给元类__main__.AB的{​​{1}}字典中看到了它们。方法(记住,这只是元类的__new__返回的现在已填充的字典)。 和以前一样,传递过程是通过元元类__init__发生的。我们还在整个attrs中看到了一个关键字参数。在属性字典中,我们还可以观察到神奇的__new__条目:有关这是什么的简要说明,请参见Provide __classcell__ example for Python 3.6 metaclass,但是, super -简短地说,是为了使__init__正常工作。

将关键字参数传递给所有三个元类方法,以及元元类的方法。 (我不太清楚为什么。请注意,在任何 metaclass 方法中修改字典都不会对其产生任何影响,因为它每次都是原始关键字参数的副本。但是,我们可以在元元类中对其进行修改:将__prepare__添加到M0.__call__进行观察。

现在我们有了类{'foo': 3}__classcell__,我们可以继续创建类super()的实际实例的过程。现在我们看到元类的kwargs.pop('foo', None)被调用(而不是元元类的):

M0.__call__

可以更改传递的AB,但我不能。上面的示例代码结束了对B的调用(通过__call__的魔力)。依次调用about to create instance b = B() Meta __call__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7} args

kwargs

完成对类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 来创建实例-这或多或少是所有Python版本的要求;您只能在返回单例实例(理想情况下,这是不可修改的实例)时“作弊”。 B在此对象上调用b,并传递了我们传递给它的参数和关键字参数。如果我们将B.__new__的{​​{1}}替换为:

return super().__new__(cls)

我们将看到object.__new__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 ,则元类应首先调用它,通常通过调用b再次调用。

旁注:the documentation说什么

引用第3.3.3.6节:

  

通过执行类主体填充了类名称空间后,将通过调用__call__创建类对象(此处传递的其他关键字与传递给__init__的关键字相同)。

这解释了在创建type.__call__作为类super().__call__的实例时对__new__的调用,但不是Python在调用{{1}之前先调用type.__call__的事实}和metaclass(name, bases, namespace, **kwds)本身创建类__prepare__Meta.__call__时。

下一段提到b条目;接下来是描述BM0.__call__钩子的用法。在这里,没有任何内容告诉我们Python如何或为什么调用Meta.__new__

在更早的版本3.3.3.3至3.3.3.5中,文档描述了确定元类,准备类名称空间以及执行类主体的过程。这是应该描述元元类动作 的地方,但不是。

其他几个部分描述了一些其他约束。重要的一个是3.3.10,它讨论了如何通过对象类型找到特殊的方法,绕过常规的成员属性查找,甚至(有时)元类getattribute,都说:

  

以这种方式绕过Meta.__init__机器为解释器内的速度优化提供了很大的空间,其代价是在处理特殊方法时需要一定的灵活性(必须设置特殊方法 在类对象本身上,以便被解释程序一致地调用。)

更新2:这实际上是诀窍的秘密:通过类型的类型可以找到特殊的A方法。如果元类具有元类,则元元类提供B槽;否则,元类的类型为__classcell__,因此__set_name__槽为__init_subclass__

答案 1 :(得分:2)

尽管@torek的回答很宽泛,并且在类创建方面有很多 other 详细信息,但您汇总到该问题的内容基本上是正确的。

您的代码中唯一有问题的地方是,可能使您感到困惑的是,您调用的Meta类本身必须是SubMeta中的元类父母。

只需将Submeta声明更改为:

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

(也不需要从“ Meta”继承-它只能从type派生。否则,尽管想到对type.__call__的自定义在同一情况下会很有用创建类实例的时间(即调用SubMeta.__call__和类本身(称为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__

此外

由此可见,使用元元类,可以自定义元类__init____new__的调用顺序和参数,而但是仍然是无法从纯Python代码中自定义的步骤,并且需要对API的本地调用(以及可能的原始对象结构操作)-

  • 一个人无法控制对__prepare__的呼叫
  • 无法控制对创建的类的__init_subclass__的调用
  • 一个人可以控制何时调用描述符的__set_name__

最后两项是在meta-meta __call__返回之后以及恢复流到类模块所在模块的流之前发生的。