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

Posted

技术标签:

【中文标题】调用 super() 时元类如何与 MRO 列表一起工作?【英文标题】:how does metaclass work with the MRO list when super() is called? 【发布时间】:2019-11-03 14:23:12 【问题描述】:

我真的被下面的代码示例弄糊涂了:

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列表为:[&lt;class '__main__.Car'&gt;, &lt;class 'object'&gt;]

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

我真的很困惑:

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

2.如果super(Meta_1, cls).__self__Car 类,那么第7 行意味着它是Car__call__ 被调用?但是调用Car 类首先会将我们带到第 1 行,对吗?那不是一个循环吗?

【问题讨论】:

__call__Meta_1的实例方法,CarMeta_1的实例。这意味着Car()Meta_1.__call__(Car) 的缩写。 Meta_1 不在 cls 的 MRO 中,原因与 type 不在“普通”类的 MRO 中的原因相同。 @chepner 谢谢。那我们为什么要把Meta_1作为super()的第一个参数呢? 因为该调用是Meta_1.__call__ 定义的一部分。这与您在 Car.__init__ 中使用 super(Car, self).__init__ 的原因相同。 type.__call__(在Meta_1 中覆盖)的目的是确保调用类的__new__ 方法,以便您可以编写Car() 而不是Car.__new__() .使用super().__call__ 确保type.__call__(最终)被调用,而不是你必须自己调用cls.__new__ 【参考方案1】:

您混淆了一些概念。首先是混淆了元类和类继承层次结构。

这两件事都是正交的——查看Car 的mro 将显示该类的继承树,并且不包括元类。换句话说,任何Meta_1 都不应该在MRO(或继承树)中。

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

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

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

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

关于你的第二个问题:

    如果 super(Meta_1, cls).__self__ 是 Car 类,那么第 7 行意味着它是 Car 的 __call__ 被调用?但是叫车 上课一开始就把我们带到了1号线,对吧?那不是一个 循环?

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

【讨论】:

感谢您的回答。在这句话中你说super:“....它必须能够做的一件事就是在它自己的__self__ 属性中注释它被调用的实例。”,我' m 困惑:你的意思是在第 6 行,super 自己的__self__ 属性正在注释调用super 的实例? 顺便说一句:“...它是一个代理对象,它将依赖于任何属性检索或对适当超类上的方法和属性的方法调用......”。使用“依靠”这个词是错字吗?你的意思是“继电器”?对不起,我不是以英语为母语的人 我还是不明白为什么Meta_1作为第一个参数传递给super() super 的第一个参数几乎总是你定义的方法所在的类,以至于在 Python 3 中,如果你简单地调用 super(),这是默认值,没有论据。 (在 MRO 中使用不同的类是有原因的,但它们很少见,此时您真的不必担心。) super 的第一个参数作为 MRO 搜索的起点; super(A, obj) 为对象 obj 的 MRO 中的类 after A 返回一个代理。【参考方案2】:

注意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() 无关紧要,因为这是clsinstances 使用的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__ 可以返回。

【讨论】:

这个答案总结了这个问题的所有cmets和答案。解释得很好! 快速提问:当您说“....cls 是作为第一个参数传递给Meta_1.__call__ 的某个对象时。这意味着我们将使用type(cls) 提供的MRO .. ...”,这里 super() 尚未在第 1 行调用,所以我看不出将 cls 作为第一个参数如何导致我们使用 type(cls) 的 MRO 而不是 cls。你能详细说明一下吗?【参考方案3】:

这是来自Michael Ekoka 的原始帖子的一个很好的答案,我的示例代码来自: Using the __call__ method of a metaclass instead of __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__)。

【讨论】:

以上是关于调用 super() 时元类如何与 MRO 列表一起工作?的主要内容,如果未能解决你的问题,请参考以下文章

类继承super原理

Python基础13_类与类型, MRO, C3算法, super()

super使用简介

python语言中多继承中super调用所有父类的方法以及要用到的MRO顺序

11_6 Python高级

在继承元类的类中调用 super 会引发错误