调用super()时,元类如何与MRO列表一起使用?

时间:2019-06-20 17:55:52

标签: python super metaclass method-resolution-order

以下代码示例让我很困惑:

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中找到Meta_1 Car。那么super(Meta_1, cos)为什么要带我们调用type.__call__呢?

2。如果super(Meta_1, cls).__self__Car类,那么第7行意味着正在调用的是Car的{​​{1}}?但是,调用__call__类将我们带到了第一行,对吗?那不是循环吗?

3 个答案:

答案 0 :(得分:1)

您正在混淆一些概念。首先是将元类与类继承层次结构混淆。

两者都是正交的-查看Car的mro将为您显示该类的继承树,并且该继承树不包括元类。换句话说,无论如何,任何Meta_1都不应位于MRO(或继承树)中。

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

因此,像调用Python中的任何实例一样,调用类对象将在其类__set_name__方法中运行代码。对于类而言,碰巧“调用”该类是创建新实例的方式-这就是元类的__call__

您误解的另一件事是__call__对象。 super()实际上不是超类,也不是超类的实例-而是代理对象,它将把任何属性检索或方法调用中继到适当的超类上的方法和属性。作为机制Super()的一部分,它可以用作代理,并具有 instance (在实例中称为其自己的super()属性)。换句话说,__self__属性是__self__调用返回的(代理)对象上的普通属性-从第二个参数中选择,或者在Python 3中自动获取-并且在以下情况下在内部使用super()对象用作代理来获取行为,就好像它正在访问该实例的“超类”上的属性或方法一样。 (在super中注释的实例)。

当您在元类中使用__self__时,代理的类是元类的超类,即super(),而不是Car的超类,type

第二个问题:

  
      
  1. 如果object是Car类,那么第7行意味着正在调用的是汽车的super(Meta_1, cls).__self__?但是打电话给汽车   上课首先把我们带到了第一行,对吧?那不是一个   循环吗?
  2.   

如上所述,从元类'__call__进行的super()调用将调用__call__,它将获得类type.__call__作为其Car参数。该方法将依次运行clsCar.__new__作为实例化类的常规过程。

答案 1 :(得分:1)

请务必注意将什么值用作super的每个参数。 super的主要目的是根据某种方法解析顺序(MRO)执行属性查找。第二个参数确定要使用哪个MRO;首先确定开始的位置。

MRO始终由 class 定义;在对实例执行方法解析时,我们使用该实例为类型的类的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__的调用,以便创建cls可以返回的Meta_1.__call__的实例。

答案 2 :(得分:0)

这是Mathias R. Jessen的原始帖子的一个很好的答案,我的示例代码来自: Michael Ekoka

基本上,我需要对super()的工作方式有更好的了解。

引用:

super的确会使用cls查找MRO,但不是人们可能想的那样。我猜想您认为它会做与cls.__mro__一样直接的事情并找到Meta_1。并非如此,这是您要解决的Class_1的MRO,一个不同的,不相关的MRO,而Meta_1不是它的一部分(Class_1并非继承自{ {1}})。 Meta_1甚至拥有cls属性,由于它是一个类,因此只是偶然。相反,__mro__将查找super的类(在我们的情况下为元类),即cls,然后从那里查找MRO(即Meta_1)。