python3元类的调用顺序

2024-06-28 10:49:51 发布

您现在位置:Python中文网/ 问答频道 /正文

当我试图理解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()

然而,结果似乎并非如此。在

^{pr2}$

任何帮助都将不胜感激。在


Tags: nameselfnewreturndefcallpreparemeta
2条回答

尽管@torek给出了一个冗长的答案,还有很多关于类创建的其他细节,但是您对这个问题的总结基本上是正确的。在

您的代码中唯一错误的地方就是您调用的Meta必须是来自SubMeta元类,而不是其父类。在

只需将Submeta声明更改为:

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

(也不需要从“Meta”继承-它只能从type派生。另外,考虑对type.__call__的定制,它将同时用于创建类的实例(即调用SubMeta.__call__)和类本身(调用Meta.__call__))的实例

这是我刚刚在终端输入的另一个简短示例。很抱歉命名不一致,也不太完整-但它显示了要点:

^{pr2}$

在处理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__返回之后,在恢复流到类模块所在的模块之前。在

诀窍,确定

更新2:基于行为,下面调用M0.__call__的事实必须是CPython源(Python/bltinmodule.c)中builtin__build_class中的this line的副作用。在

为了定义一个具有元类的类,我们通常称元类的__prepare____new__、和{}。这将在下面的示例中创建一个类Meta-它是可调用的,但是它的内部PyFunction_GET_CODE槽不是指向它自己的__call__,而是指向其元类的__call__。因此,如果我们调用Meta()(元类对象),我们调用M0.__call__

print("call Meta")
print("Meta returns:", Meta('name', (), {}))
print("finished calling Meta")

产生:

^{pr2}$

换句话说,我们看到Meta的行为与type相似,但是它(相当神奇而且没有很好的文档记录)调用了M0.__call__。这无疑是由于在类的类型中查找__call__,而不是在类的实例中查找(实际上除了我们正在创建的实例之外,没有实例)。这实际上是一般情况:我们在Meta类型上调用__call__,而{}的类型是M0

print("type(Meta) =", type(Meta))

印刷品:

type(Meta) = <class '__main__.M0'>

这就解释了这是从哪里来的。(我仍然认为应该在文档中强调这一点,文档还应该描述对元类类型的约束,这些约束是在^{} in ^{}in ^{} in Objects/typeobject.c中实施的。)

更新原始答案

我不知道您的图表来自何处,但它是错误的。更新:实际上,您可以为您的元类创建一个元类;请参阅jsbueno's answer,我已经更新了下面的示例。新的句子/文本都是用粗体,除了最后一节描述我对明显缺乏文档的困惑。在

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

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

另一件可能有帮助的事情:注意,元类__prepare__方法在创建类B的任何实例之前被调用:它在class B本身被定义时被调用。为了说明这一点,这里有一个更正的元类和类。我还增加了一些插图。基于jsbueno的答案,我还添加了一个元元类。我找不到正式的Python文档,但是我已经更新了下面的输出。

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

现在,让我们观察一下当我运行这个程序时会发生什么,并将每个部分分开:

$ 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本身,Python首先调用元类的__prepare__,向它传递类的名称(A)、基类列表(一个空元组,它称为list,但实际上是一个tuple)和任何关键字参数(none)。正如pep3115所指出的,元类需要返回一个dictionary或dict类对象;这个类只返回一个空字典,所以我们在这里做得很好。在

(我不在这里打印cls本身,但是如果你这样做,你会看到它只是<class '__main__.Meta'>。)

接下来,从__prepare__获取字典后,Python首先调用meta __call__,即M0.__call__,将整组参数作为args元组传递。然后用该类的所有属性填充__prepare__提供的字典,将其作为attrs传递给元类__new__和{}。如果打印从__prepare__返回并传递给__new__和{}的字典的id,您将看到它们都匹配。在

由于类A没有方法或数据成员,我们在这里只看到神奇的__module__和{}属性。我们也没有看到关键字参数,所以现在让我们继续创建类B

about 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__.A。类B还定义了几个方法(__new__和{}),我们可以在传递给元类__new____init__方法的{}字典中看到它们(记住,它们只是元类的__prepare__返回的现在填充的字典)。和以前一样,传递是通过元元类M0.__call__进行的。我们还看到一个贯穿始终的关键字参数{'foo': 3}。在属性字典中,我们还可以观察到神奇的__classcell__条目:关于这是关于什么的简短描述,请参见Provide __classcell__ example for Python 3.6 metaclass,但是,呃,super-short,它是为了使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}

可以更改传递的argskwargs,但我没有;上面的示例代码最终调用了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

它完成了类B的新实例的实现,然后我们将其绑定到名称b。在

请注意,B.__new__表示:

return super().__new__(cls)

因此,我们调用object.__new__来创建实例这或多或少是所有Python版本的一个要求;只有当您返回一个singleton实例(理想情况下,是不可修改的)时,您才能“作弊”。它是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。因此,元类__call__方法调用底层类的__init__,通常是通过super().__call__调用type.__call__。如果底层类有一个__new__,元类应该首先调用它,通常是通过调用type.__call__。在

旁注:什么the documentation

引用第3.3.3.6节:

Once the class namespace has been populated by executing the class body, the class object is created by calling metaclass(name, bases, namespace, **kwds) (the additional keywords passed here are the same as those passed to __prepare__).

这解释了在将b创建为类B的实例时对Meta.__call__的调用,但不能解释Python在创建类A和{}时首先调用M0.__call__,然后调用{}和{}。在

下一段提到__classcell__条目;后面的一段继续描述__set_name__和{}钩子的使用。{or Python在这一点上什么也没有告诉我们。在

在前面的3.3.3.3到3.3.3.5节中,文档描述了确定元类、准备类名称空间和执行类主体的过程。这就是元元类动作应该被描述的地方,但事实并非如此

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

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

更新2:这确实是诀窍的秘密:特殊的__call__方法是通过类型的类型找到的。如果元类有元类,则元类提供__call__槽;否则元类的类型是type,因此{}槽是{}。

相关问题 更多 >