1

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

“超级”在 Python 中做了什么?

python, 继承, super() 方法

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

4

3 回答 3

5

如果你想知道为什么 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()时,显然是在调用您在B'test方法中定义的代码。并且B.__mro__(__main__.B, __main__.A, object)。那么,这怎么super(B, self).test()可能调用C'test方法而不是A' 呢?

这里的关键是 MRO 基于 的类型,self而不是基于定义方法的类型。如果您进入函数内部,您会看到它是,而不是。Btestprint(type(self))testDB

因此,super(B, self)实际上获取self.__class__.__mro__(在本例中为D.__mro__),在列表中查找B,并返回其后的下一个内容。相当简单。

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

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

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

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

那么,结果如何D().test呢?这是在哪里创建的?这就是有趣的部分。有关完整描述,请阅读Descriptor HowTo Guide,但简要说明:im_selfim_class

每当您编写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)

最后是.get()魔术位。如果你看一个函数——比如 ,B.test.im_func你会发现它实际上有一个get方法。它所做的就是创建一个绑定方法,以im_funcas 自身、im_class作为 classobj.__class__im_self作为 object obj

于 2013-09-11T01:51:26.457 回答
4

简短的回答是方法解析顺序大致是“广度优先”。也就是说,它在到达它们的任何超类之前会遍历给定祖先级别的所有基类。因此,如果 D 继承自 B 和 C,它们都继承自 A,则 MRO 总是在 A 之前有 BC。

另一种思考方式是,如果订单是 B->A,那么A.test将在 之前调用C.test,即使C是 的子类A。您通常希望在超类之前调用​​子类实现(因为子类可能希望完全覆盖超类并且根本不调用它)。

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

于 2013-09-11T00:39:54.180 回答
3

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()它的用途。

于 2013-09-11T01:03:02.827 回答