了解python的super方法

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

我在这里研究了关于python的super()方法的其他问题,但是我仍然发现很难理解整个概念。

我也在看《专业python》一书中的例子。

这里引用的例子是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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'

书中的解释是

In the most common case, which includes the usage shown here, super() takes two arguments: a
class and an instance of that class. As our example here has shown, the instance object determines
which MRO will be used to resolve any attributes on the resulting object. The provided class determines
a subset of that MRO, because super() only uses those entries in the MRO that occur after the class
provided.

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

使用uu init_uu()方法了解python super()。

"super"在python中做什么?

python,继承,super()方法

[Python]:被super()迷惑了


如果您想知道为什么python选择这个特定的mro算法,可以在邮件列表存档中进行讨论,并在python 2.3方法解析顺序中简要总结。

但事实上,归根结底是这样的:在处理多重继承时,python 2.2的方法分辨率被破坏了,任何人建议修复它的第一件事就是从dylan中借用c3算法,没有人对此有任何问题,也没有人提出更好的建议,因此python使用c3。

如果你对c3相对于其他算法的一般优势(和劣势)更感兴趣…

布伦巴恩和弗洛雷克的回答为这个问题提供了基础。python的super()被认为是super!雷蒙德·赫廷格的博客以同样的方式进行了更长更详细的讨论,绝对值得一读。

Dylan的单调超类线性化是描述该设计的原始论文。当然,Dylan是一种与Python非常不同的语言,这是一篇学术论文,但其基本原理仍然很好。

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

而且,您还需要学习很多关于可选方案的知识,以及它们是如何使用的以及不适合于Python的,以便进一步了解。或者,如果你想要更深入的信息,你需要问更具体的问题。

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

当您调用D().test()时,显然是在调用您在Btest方法中定义的代码。其中B.__mro__(__main__.B, __main__.A, object)。那么,这个super(B, self).test()怎么可能调用Ctest方法而不是A方法呢?

这里的关键是,MRO是基于self的类型,而不是基于B类型,其中定义了test方法。如果你在test函数中使用print(type(self)),你会看到它是D,而不是B

因此,super(B, self)实际得到self.__class__.__mro__(本例为D.__mro__),在列表中找到B,然后返回下一个结果。相当简单。

但这并不能解释MRO是如何工作的,只是它的作用。D().test()如何从B调用该方法,但在selfD的情况下?

首先,请注意,D().testD.testB.test不是同一个函数,因为它们根本就不是函数;它们是方法。(我假设这里是python 2.x。事情有点不同,主要是3.x中的简单。)

方法基本上是一个对象,包含im_funcim_classim_self成员。当你调用一个方法时,你所要做的就是调用它的im_func,它的im_self(如果不是None)在开始时作为一个额外的参数塞入。

所以,我们的三个例子都有相同的im_func,它实际上是您在B中定义的函数。但前两个是D而不是B用于im_class,第一个是D实例而不是None用于im_self。所以,这就是为什么调用它最终会传递D实例作为self

那么,D().test是如何以im_selfim_class结束的呢?在哪里创建的?这是有趣的部分。要获得完整的描述,请阅读"如何操作指南"描述符,但简单地说:

每当编写foo.bar时,实际发生的情况就相当于调用getattr(foo, 'bar'),它的作用类似于这样(忽略实例属性,__getattr____getattribute__、slots、builtins等):

1
2
3
4
5
6
7
8
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__im_self为对象obj


简而言之,该方法的分辨率顺序大致为"宽度优先"。也就是说,它在进入任何一个超类之前,都要经过给定祖先级别的所有基类。因此,如果D继承B和C,两者都继承A,那么MRO总是在A之前有B和C。

另一种思考方法是,如果命令是b->a,那么A.test将在C.test之前被调用,即使CA的一个子类。通常,您希望在超类1之前调用子类实现(因为子类1可能希望完全重写超类,而根本不调用它)。

这里可以找到更详细的解释。您还可以通过谷歌搜索或搜索stackoverflow来查找关于"python方法解析顺序"或"python mro"的问题。


super()基本上就是您告诉Python"做这个对象的其他类所说的事情"。

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

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

一个(有些荒谬的)例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()的目的。