调用 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行的cls
为Car
类,其MRO列表为:[<class '__main__.Car'>, <class 'object'>]
但是,第 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
的实例方法,Car
是Meta_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()
无关紧要,因为这是cls
的instances 使用的MRO。
Meta_1
本身的MRO可以用
>>> Meta_1.mro(Meta_1)
[<class '__main__.Meta_1'>, <class 'type'>, <class 'object'>]
(mro
是type
类的实例方法,因此需要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 列表一起工作?的主要内容,如果未能解决你的问题,请参考以下文章
Python基础13_类与类型, MRO, C3算法, super()