【问题标题】:Understanding python's super method, Why D().test() will return 'B->C' and not 'B-->A'了解python的超级方法,为什么D().test()会返回'B->C'而不是'B-->A'
【发布时间】:2013-09-11 00:35:31
【问题描述】:

我在这里查看了有关 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 中出现在类之后的那些条目 提供。

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

Understanding Python super() with __init__() methods

What does 'super' do in Python?

python, inheritance, super() method

[python]: confused by super()

【问题讨论】:

  • 您是否想了解 (1) 确定顺序的规则是什么,(2) 为什么要发明该规则,或者 (3) 为什么 Python 选择该规则而不是替代规则?
  • 我现在知道 [1],还在寻找 [2] 和 [3]。

标签: python super


【解决方案1】:

如果你想知道Python为什么选择这个特定的MRO算法,讨论在mailing list archives,并在The Python 2.3 Method Resolution Order进行简要总结。

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


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

BrenBarn 和 florquake 的回答为这个问题提供了基础知识。来自 Raymond Hettinger 博客的Python's super() considered super! 是一个更长更详细的讨论,绝对值得一读。

A Monotonic Superclass Linearlization for Dylan 是描述该设计的原始论文。当然,Dylan 是一种与 Python 非常不同的语言,这是一篇学术论文,但基本原理还是很不错的。

最后,The Python 2.3 Method Resolution Order(上面链接的相同文档)对好处进行了一些讨论。

您需要了解很多关于替代方案的知识,以及它们如何适合和不适合 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 不是同一个函数,因为它们根本不是函数;它们是方法。 (我假设这里是 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 的呢?这是在哪里创建的?这就是有趣的部分。如需完整说明,请阅读Descriptor HowTo Guide,但简要说明:

每当你写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_func 为自身,im_class 为类obj.__class__im_self 为对象obj

【讨论】:

  • 非常感谢您的详细解释。
【解决方案2】:

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

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

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

【讨论】:

  • 请注意,它实际上并不是广度优先。这是确保被覆盖的方法永远不会被调用来代替应该覆盖它们的方法的某种顺序,并且类的超类列表中较早的类中的方法会覆盖类的超类列表中稍后的类中的方法。广度优先方法解决方案无法实现这一目标。
  • 是的,这就是为什么这是简短的答案,但我将其编辑为“大致”。
  • 关于 C3 的原始 Dylan 文档比 Python 文档更容易阅读,但我再也找不到 Dylan 在线文档了,只有他们提到的ACM paper 以获取更多详细信息。
【解决方案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() 的用途。

【讨论】:

    猜你喜欢
    • 2012-01-27
    • 1970-01-01
    • 2018-09-14
    • 2011-02-02
    • 2021-09-10
    • 2014-03-29
    • 2013-10-07
    • 1970-01-01
    • 2021-08-26
    相关资源
    最近更新 更多