当我试图理解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()
然而,结果似乎并非如此。在
^{pr2}$任何帮助都将不胜感激。在
尽管@torek给出了一个冗长的答案,还有很多关于类创建的其他细节,但是您对这个问题的总结基本上是正确的。在
您的代码中唯一错误的地方就是您调用的
Meta
必须是来自SubMeta
的元类,而不是其父类。在只需将
Submeta
声明更改为:(也不需要从“Meta”继承-它只能从
type
派生。另外,考虑对type.__call__
的定制,它将同时用于创建类的实例(即调用SubMeta.__call__
)和类本身(调用Meta.__call__
))的实例这是我刚刚在终端输入的另一个简短示例。很抱歉命名不一致,也不太完整-但它显示了要点:
^{pr2}$在处理
klass
正文时,Python输出是:而且
从这里可以看出,使用元元类,可以自定义元类
__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__
:产生:
^{pr2}$换句话说,我们看到}的类型是
Meta
的行为与type
相似,但是它(相当神奇而且没有很好的文档记录)调用了M0.__call__
。这无疑是由于在类的类型中查找__call__
,而不是在类的实例中查找(实际上除了我们正在创建的实例之外,没有实例)。这实际上是一般情况:我们在Meta
的类型上调用__call__
,而{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文档,但是我已经更新了下面的输出。现在,让我们观察一下当我运行这个程序时会发生什么,并将每个部分分开:
为了创建类
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
:这个比较有趣。现在我们有一个基类,即}),我们可以在传递给元类}字典中看到它们(记住,它们只是元类的
__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__
中观察这一点。)现在我们有了
A
和B
,我们可以开始创建B
类的实际实例的过程。现在我们看到元类的__call__
被调用(而不是元类):可以更改传递的
args
或kwargs
,但我没有;上面的示例代码最终调用了type.__call__(cls, *args, **kwargs)
(通过super().__call__
的魔力)。这依次调用B.__new__
和B.__init__
:它完成了类
B
的新实例的实现,然后我们将其绑定到名称b
。在请注意,
B.__new__
表示:因此,我们调用
object.__new__
来创建实例这或多或少是所有Python版本的一个要求;只有当您返回一个singleton实例(理想情况下,是不可修改的)时,您才能“作弊”。它是type.__call__
调用这个对象上的B.__init__
,传递我们传递给它的参数和关键字参数。如果我们将Meta
的__call__
替换为:我们将看到
B.__new__
和B.__init__
从未被调用过:实际上,这将创建一个无用/未初始化的实例
b
。因此,元类__call__
方法调用底层类的__init__
,通常是通过super().__call__
调用type.__call__
。如果底层类有一个__new__
,元类应该首先调用它,通常是通过调用type.__call__
。在旁注:什么the documentation说
引用第3.3.3.6节:
这解释了在将}时首先调用}和{}。在
b
创建为类B
的实例时对Meta.__call__
的调用,但不能解释Python在创建类A
和{M0.__call__
,然后调用{下一段提到}钩子的使用。{or Python在这一点上什么也没有告诉我们。在
__classcell__
条目;后面的一段继续描述__set_name__
和{在前面的3.3.3.3到3.3.3.5节中,文档描述了确定元类、准备类名称空间和执行类主体的过程。这就是元元类动作应该被描述的地方,但事实并非如此
几个附加部分描述了一些附加约束。其中一个重要的例子是3.3.10,它讨论了如何通过对象类型找到特殊方法,绕过常规的成员属性查找,甚至(有时)绕过元类getattribute,说:
更新2:这确实是诀窍的秘密:特殊的}槽是{}。
__call__
方法是通过类型的类型找到的。如果元类有元类,则元类提供__call__
槽;否则元类的类型是type
,因此{相关问题 更多 >
编程相关推荐