元类在调用 super() 时如何处理 MRO 列表



我对以下代码示例感到非常困惑:

class Meta_1(type):
def __call__(cls, *a, **kw):             # line 1
print("entering Meta_1.__call__()")  
print(cls)                           # line 4
print(cls.mro())                     # line 5
print(super(Meta_1, cls).__self__)   # line 6
rv = super(Meta_1, cls).__call__(*a, **kw)  # line 7
print("exiting Meta_1.__call__()")
return rv

class Car(object, metaclass=Meta_1):
def __new__(cls, *a, **kw):
print("Car.__new__()")
rv = super(Car, cls).__new__(cls, *a, **kw)
return rv
def __init__(self, *a, **kw):
print("Car.__init__()")
super(Car,self).__init__(*a, **kw)
if __name__ == '__main__':
c = Car()

此代码的打印消息是:

entering Meta_1.__call__()
<class '__main__.Car'>                      # line 4
[<class '__main__.Car'>, <class 'object'>]  # line 5
<class '__main__.Car'>                      # line 6
Car.__new__()
Car.__init__()
exiting Meta_1.__call__()

结果表明,第 4 行的clsCar类,其 MRO 列表为:
[<class '__main__.Car'>, <class 'object'>]

但是,第 6 行显示super(Meta_1, cls).__self__也是Car类。

我真的很困惑:

  1. 在第 7 行中,似乎super(Meta_1, cls).__call__(*a, **kw)最终会导致type.__call__. 但是,据我所知,super(arg1, arg2)将研究第二个输入参数的 MRO 以找到第一个输入参数,并将下一个类返回给它。但是在我的代码的第 6 行和第 7 行中,第二个参数(Car) 的 MRO 不包含第一个输入参数(Meta_1),您在 MRO 中找不到CarMeta_1。那么super(Meta_1, cos)为什么要带我们调用type.__call__呢?

2. 如果super(Meta_1, cls).__self__Car类,那么第 7 行意味着它被称为Car__call__? 但是打电话给Car班首先把我们带到了1号线,对吧?那不是循环吗?

你混淆了一些概念。第一个是将元类与类继承层次结构混淆。

这两件事都是ortogonal - 查看Car的MRO将显示该类的继承树,其中不包括元类。换句话说,任何Meta_1都不应以任何方式位于 MRO(或继承树)中。

元类是类的类型 - 也就是说,它具有创建类对象本身的模板和方法。因此,它具有构建类 MRO 本身的"机制",并调用类的__new____init__(以及__init_subclass__和初始化调用其__set_name__的描述符)。

因此,调用类对象,就像在 Python 中调用任何实例一样,将在它的类__call__方法中运行代码。在类的情况下,碰巧"调用"类是创建新实例的方法 - 这是元类的__call__

。你误解的另一件事是super()对象。Super()实际上不是超类,也不是超类的实例 - 它是一个代理对象,它将任何属性检索或方法调用中继到正确超类上的方法和属性。作为super()用来充当代理的机制的一部分,是将调用它的实例作为自己的__self__属性。换句话说,__self__属性是调用返回的(代理)对象super()普通属性 - 它是从第二个参数中选取的,或者在 Python 3 中自动选择的 - 当super对象用作代理时,它在内部使用,以获得就好像它正在访问该实例的"超类"上的属性或方法一样。(在__self__中注释的实例)。

当你在元类中使用super()时,代理的类是元类的超类,它是type,而不是Car的超类,object

所以你的第二个问题:

  1. 如果super(Meta_1, cls).__self__是 Car 类,那么第 7 行表示它被称为 Car 的__call__?但是打电话给汽车 上课首先把我们带到了1号线,对吧?那不是 圈?

如上所述,来自元类__call__super()调用将调用type.__call__,并将类Car作为其cls参数。反过来,该方法将运行Car.__new__Car.__init__作为实例化类的正常过程。

重要的是要注意哪些值被用作要super的每个参数。super的主要目的是根据某些方法解析顺序 (MRO) 执行属性查找。第二个参数确定要使用的MRO;第一个确定从哪里开始寻找。

MRO 始终由类定义;在实例上执行方法解析时,我们使用该实例所属类型的的 MRO。

在课堂上

class Meta_1(type):
def __call__(cls, *a, **kw):             # line 1
print("entering Meta_1.__call__()")  
print(cls)                           # line 4
print(cls.mro())                     # line 5
print(super(Meta_1, cls).__self__)   # line 6
rv = super(Meta_1, cls).__call__(*a, **kw)  # line 7
print("exiting Meta_1.__call__()")
return rv

我们看到super的两种用途 .两者都采用相同的论点。cls是作为第一个参数传递给Meta_1.__call__的对象。这意味着我们将使用type(cls)提供的 MRO,并且我们将使用在Meta_1之后找到的第一个提供所需方法的类。(在第一次调用中,__self__是代理对象本身的属性,而不是代理super返回的类的属性或方法。

运行代码时,您会看到cls绑定到Car类型对象。那是因为Car()是由type(Car).__call__()实现的;由于Car使用Meta_1作为其元类,因此type(Car)Meta_1

cls.mro()

无关紧要,因为这是cls实例使用的MRO。

Meta_1本身的MRO可以看到

>>> Meta_1.mro(Meta_1)
[<class '__main__.Meta_1'>, <class 'type'>, <class 'object'>]

(mrotype类的实例方法,因此需要看似多余的type实例作为参数。请记住,cls.mro()等效于type(cls).mro(cls)

所以第 7 行是对type.__call__的调用,以便创建一个Meta_1.__call__可以返回的cls实例。

这是Michael Ekoka原始帖子的一个很好的答案,我的示例代码来自其中: 使用元类的__call__方法而不是__new__?

基本上,我需要更好地了解super()的工作原理。

报价:

super确实会使用cls来找到MRO,但不是人们可能想象的那样。我猜你认为它会做一些像cls.__mro__一样直接的事情并找到Meta_1.不是这样,这就是您通过这样做解决的Class_1的 MRO,一个不同的、不相关的 MRO,Meta_1不是它的一部分(Class_1不会继承自Meta_1)。cls即使拥有__mro__财产也只是一个意外,因为它是一个阶级。相反,super将查找cls的类(在我们的例子中是元类),即Meta_1,然后将从那里查找 MRO(即Meta_1.__mro__)。

相关内容

  • 没有找到相关文章

最新更新