理解python的超级方法,为什么D().test()将返回'B-> C'而不是'B - > A'

Saa*_*aad 1 python super

我在这里看了关于python的super()方法的其他问题,但我仍然觉得很难理解整个概念.

我也在查看pro python一书中的例子

引用的例子是

class A(object):
     def test(self):
         return 'A'

class B(A):
     def test(self):
         return 'B-->' + super(B, self).test()

class C(A):
     def test(self):
         return 'C'

class D(B, C):
      pass

>>> A().test()
'A'
>>> B().test()
'B-->A'
>>> C().test()
'C'
>>> D().test()
'B-->C'

>>> A.__mro__
(__main__.A, object)

>>> B.__mro__
(__main__.B, __main__.A, object)

>>> C.__mro__
(__main__.C, __main__.A, object)

>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
Run Code Online (Sandbox Code Playgroud)

为什么要做D().test()我们得到'B - > C'而不是'B - > A'的输出

书中的解释是

在最常见的情况下,包括此处显示的用法,super()接受两个参数:类和该类的实例.正如我们在此处的示例所示,实例对象确定将使用哪个MRO来解析结果对象上的任何属性.提供的类确定该MRO的子集,因为super()仅使用在提供的类之后发生的MRO中的那些条目.

我仍然觉得解释有点难以理解.这可能是一个可能的重复,并且已经多次询问与此类似的问题,但如果我理解了这一点,我可能能够更好地理解其他问题.

使用__init __()方法理解Python super()

"超级"在Python中做了什么?

python,继承,super()方法

[python]:被super()搞糊涂了

aba*_*ert 5

如果您想知道Python选择此特定MRO算法的原因,请参阅邮件列表存档,并在Python 2.3方法解析顺序中进行简要总结.

但实际上,它归结为:Python 2.2的方法解决方案在处理多重继承时被打破,并且任何人建议修复它的第一件事是从Dylan借用C3算法,没有人对它有任何问题或建议更好的,因此Python使用C3.


如果你对C3对其他算法的一般优点(和缺点)更感兴趣......

BrenBarn和florquake的答案为这个问题提供了基础知识.Python的super()被认为超级!来自Raymond Hettinger的博客是一个更长,更详细的讨论,同样绝对值得一读.

Dylan的Monotonic Superclass Linearlization是描述设计的原始论文.当然Dylan是一种与Python非常不同的语言,这是一篇学术论文,但理由仍然相当不错.

最后,Python 2.3方法解析顺序(上面链接的相同文档)对其好处进行了一些讨论.

而且你需要学习很多关于替代方案的知识,以及它们如何与Python相适应,以及更进一步.或者,如果您想了解有关SO的更深入信息,您需要提出更具体的问题.


最后,如果你问的是"如何"问题:

当你打电话D().test(),这显然给你打电话定义代码Btest方法.而B.__mro__(__main__.B, __main__.A, object).那么,怎样才能即super(B, self).test()可能调用Ctest方法,而不是A的?

这里的关键是,MRO是基于类型的self,不是基于类型B,其中test定义方法.如果你print(type(self))test函数内部,你会发现它D不是B.

所以,super(B, self)实际得到self.__class__.__mro__(在这种情况下D.__mro__),B在列表中找到,然后返回它之后的下一个东西.非常简单.

但这并没有解释MRO是如何运作的,只是它的作用.如何D().test()调用方法B,但是self这是一个D

首先,请注意D().test,D.test并且B.test功能不同,因为它们根本不起作用; 他们是方法.(我在这里假设Python 2.x.事情有点不同 - 主要是在3.x中更简单.)

一种方法,基本上是与对象im_func,im_classim_self成员.当你调用一个方法时,你所做的就是调用它im_func,它的im_self(如果不是None)在开始时作为额外的参数填入.

所以,我们的三个例子都是相同的im_func,实际上你在里面定义的函数B.但前两个D而不是Bfor im_class,而第一个也有一个D实例而不是Nonefor im_self.所以,这就是调用它最终传递D实例的方式self.

那么,如何D().test与最终im_selfim_class?这会在哪里创建?这是有趣的部分.有关完整说明,请阅读Descriptor HowTo指南,但请简要说明:

当你写的foo.bar,实际上发生了什么就相当于一个电话getattr(foo, 'bar'),这确实是这样的(忽略实例的属性,__getattr__,__getattribute__,插槽,内建等):

def getattr(obj, name):
    for cls in obj.__class__.__mro__:
        try:
            desc = cls.__dict__[name]
        except KeyError:
            pass
        else:
            return desc.get(obj.__class__, obj)
Run Code Online (Sandbox Code Playgroud)

.get()最后是神奇的一点.如果你看一个函数 - 比如说,B.test.im_func你会发现它实际上有一个get方法.它的作用是创建一个绑定方法,使用im_funcas本身,im_class作为类obj.__class__,以及im_self作为对象obj.