繁体   English   中英

了解python的super方法,为什么D()。test()将返回'B-> C'而不是'B-> A'

[英]Understanding python's super method, Why D().test() will return 'B->C' and not 'B-->A'

我在这里查看了有关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)

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

书中的解释是

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

我仍然觉得解释有些难以理解。 这可能是重复的,类似的问题已经被问过很多次了,但是如果我对此有所了解,我也许能够更好地理解其他问题。

通过__init __()方法了解Python super()

“ super”在Python中做什么?

python,继承,super()方法

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

如果您想知道Python为什么选择这种特定的MRO算法,请在邮件列表档案中进行讨论,并在《 Python 2.3方法解析顺序》中进行了简要概述。

但实际上,可以归结为这一点:处理多重继承时,Python 2.2的方法解析被破坏了,任何人建议修复它的第一件事就是从Dylan借用C3算法,没有人对它有任何问题或建议更好,因此Python使用C3。


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

布伦·巴恩(BrenBarn)和弗洛格克(florquake)的答案为该问题提供了基础。 Python的super()被认为是超级! 同样来自Raymond Hettinger博客的文章讨论的时间更长,更详细,绝对值得一读。

Dylan的单调超类线性化是描述该设计的原始论文。 当然,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的类型,而不是基于定义test方法的B类型。 如果要在test函数中进行print(type(self)) ,则将看到它是D ,而不是B

因此, super(B, self)实际上获得了self.__class__.__mro__ (在本例中为D.__mro__ ),在列表中找到B ,然后返回其后的下一个对象。 很简单。

但这并不能解释MRO的工作原理,而只是解释它的工作原理。 D().test()如何从B调用方法,但selfD

首先,请注意D().testD.testB.test是不相同的函数,因为它们根本不是函数。 他们是方法 (我在这里假设使用Python2.x。在3.x中,情况有所不同-主要是更简单。)

方法基本上是具有im_funcim_classim_self成员的对象。 调用方法时,您所要做的就是调用im_func ,并将im_self (如果不是Noneim_self为开头的额外参数。

因此,我们的三个示例都具有相同的im_func ,实际上您在B内部定义的函数。 但前两个有D ,而不是Bim_class ,和第一也有D实例,而不是Noneim_self 因此,这就是调用它最终将D实例作为self传递的方式。

那么, D().test如何以那个im_selfim_class 在哪里创建的? 那是有趣的部分。 有关完整的描述,请阅读《 描述符操作指南》 ,但要简短:

每当您编写foo.bar ,实际发生的情况都等同于对getattr(foo, 'bar')的调用,该调用会执行以下操作(忽略实例属性, __getattr__ getattr(foo, 'bar') __getattr____getattribute__ ,slot,内置插件等):

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)

最后的.get()是神奇的一环。 如果您看一个函数,例如B.test.im_func ,您会发现它实际上有一个get方法。 它要做的是创建一个绑定方法,以im_func作为自身, im_class作为obj.__class__ class im_selfim_self作为对象obj

简短的答案是方法的解析顺序大约是“广度优先”。 也就是说,在进入任何父类之前,它会先经过给定祖先级别的所有基类。 因此,如果D从B和C继承,而B和C都从A继承,则MRO始终在A之前具有B C。

想想它的另一种方式是,如果为了去B-> A,然后A.test将被调用之前C.test ,即使C是的子类A 通常,您希望在超类之前先调用子类实现(因为子类可能希望完全覆盖超类而不根本不调用它)。

这里可以找到更长的解释。 您还可以通过搜索或搜索Stackoverflow中有关“ Python方法解析顺序”或“ Python MRO”的问题来找到有用的信息。

super()基本上就是您告诉Python“执行此对象的其他类说的话”的方式。

当您的每个类只有一个父类(单继承)时, super()会将您简单地引用到父类。 (我想您已经了解了这一部分。)

但是,当您使用多个基类时,就像您在示例中所做的那样,事情开始变得有点复杂。 在这种情况下,Python确保如果在任何地方都调用super() ,则将调用每个类的方法。

一个(有点荒谬的)示例:

class Animal(object):
    def make_sounds(self):
        pass

class Duck(Animal):
    def make_sounds(self):
        print 'quack!'
        super(Duck, self).make_sounds()

class Pig(Animal):
    def make_sounds(self):
        print 'oink!'
        super(Pig, self).make_sounds()

# Let's try crossing some animals
class DuckPig(Duck, Pig):
    pass

my_duck_pig = DuckPig()
my_duck_pig.make_sounds()
# quack!
# oink!

您可能希望您的DuckPigquack! oink! 毕竟是猪和鸭,对吗? 好吧,这就是super()目的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM