在尝试了解metaclass
创建类实例的顺序时,我感到很困惑。根据此图表(source),
我输入以下代码进行验证。
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__
任何帮助将不胜感激。
答案 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__
还定义了几种方法(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
的{{1}}字典中看到了它们。方法(记住,这只是元类的__new__
返回的现在已填充的字典)。 和以前一样,传递过程是通过元元类__init__
发生的。我们还在整个attrs
中看到了一个关键字参数。在属性字典中,我们还可以观察到神奇的__new__
条目:有关这是什么的简要说明,请参见Provide __classcell__ example for Python 3.6 metaclass,但是,__init__
正常工作。
将关键字参数传递给所有三个元类方法,以及元元类的方法。 (我不太清楚为什么。请注意,在任何 metaclass 方法中修改字典都不会对其产生任何影响,因为它每次都是原始关键字参数的副本。但是,我们可以在元元类中对其进行修改:将__prepare__
添加到M0.__call__
进行观察。)
现在我们有了类{'foo': 3}
和__classcell__
,我们可以继续创建类super()
的实际实例的过程。现在我们看到元类的kwargs.pop('foo', None)
被调用(而不是元元类的):
M0.__call__
可以更改传递的A
或B
,但我不能。上面的示例代码结束了对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
再次调用。
引用第3.3.3.6节:
通过执行类主体填充了类名称空间后,将通过调用
__call__
创建类对象(此处传递的其他关键字与传递给__init__
的关键字相同)。
这解释了在创建type.__call__
作为类super().__call__
的实例时对__new__
的调用,但不是Python在调用{{1}之前先调用type.__call__
的事实}和metaclass(name, bases, namespace, **kwds)
本身创建类__prepare__
和Meta.__call__
时。
下一段提到b
条目;接下来是描述B
和M0.__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__
返回之后以及恢复流到类模块所在模块的流之前发生的。