多重继承中访问超类方法的更好方式

15
class Animal(object):
    def eat(self):
        print("I eat all")

class C(object):
    def eat(self):
        print("I too eat")

class Wolf(C, Animal):
    def eat(self):
        print("I am Non Veg")
        super(Wolf, self).eat()
        Animal.eat(self)

w = Wolf()
w.eat()

我正在学习Python中的多重继承,我想在派生类中使用super方法访问AnimalC类的eat方法。

super方法的默认调用会调用C类的eat方法,但是要调用Animal类的方法,我使用了Animal.eat(self)。我的问题是如何使用super方法调用Animal类的方法。


可能是Python的super()在多重继承中如何工作?的重复问题。 - Eli Bendersky
4个回答

8
如果您想进行合理的多重继承,需要确保每个人都调用了super,除了一个基类,其职责是不调用super。在OOP理论中,拥有两个完全独立的基类具有相同方法并不是一件有意义的事情,而且Python没有处理这种情况的工具。您可能会在许多示例中看到使用两个明显独立的基类,但在这些情况下,示例方法通常是__init__,而单个不调用super的基类是object

4
在面向对象编程理论中,拥有两个完全独立的基类具有相同方法这样的情况是没有意义的,同时Python也没有很好地处理这种情况的工具。那么对于尝试装饰行为的Mixins呢? - Bharat Khatri

2
你不能使用 super 调用 Animal.eat 方法。 super 使用 Python 的方法解析顺序 (MRO) 来确定调用哪个类,而在 MRO 中,C 遮盖了 Animal
在你的例子中,super(Wolf, self).eat()C.eat(self) 有相同的效果。调用 Animal.eat(self) 可行,但前提是继承图保持当前状态。
必须调用不可用的方法是类建模需要改进的指示。

10
“super(Wolf, self).eat()” 只是 “Wolf.eat(self)” 的一种语法糖。不,这只是正常的语法。这两个调用在语义上是不同的,因为第一个会遍历 MRO 并且对于 Wolf 的不同实例可能会做出不同的事情(取决于更专业的子类化类),而第二个只是调用 Wolf.eat,在这种情况下将导致无限递归。(前者实际上在此代码中调用了 C.eat。)“调用 Animal.eat(self) 是完全可以的”,除非您想让任意继承允许仅调用所有父方法一次。 - Mike Graham
@Mike Graham 是正确的。super(Wolf, self).eat() 不是 Wolf.eat(self) 的简写,后者会递归调用子类。super(Wolf, self).eat() 调用父类的 eat() 方法。 - Alex Smith
@Mike Graham 是的,这里使用“语法糖”一词是不正确的。已经修复了将 super 展开为 C.eat 的问题,并澄清了其余部分。谢谢! - phihag
@phihag,你的编辑带来了一些好的改进,但我仍然认为你提供的建议有点靠不住。使用 super 调用 C.eat 和直接调用 Animal.eat 不能确保两者都被准确调用一次,因为 Wolf 的更多子类的 MRO 可能不是你期望的。 - Mike Graham
@Mike Graham,你能举一个子类的例子吗?假设子类没有进行任何非MRO超类调用的花招,使得super(Subclass, self).eat()不会精确地调用Animal.eatWolf.eat一次。我认为MRO的标准之一恰恰是在Wolf的任何子类的线性化中,C始终在Wolf之前。 - phihag

1

super 用于在父类中查找。在您的情况下,因为您按顺序设置了继承 C, Animal,方法查找的优先顺序如下:

  • 第一个父类
  • 第二个父类

所以 super 将返回最近父类的方法。

需要注意的是,搜索是按深度优先进行的:

>>> class A(object):
...      def t(self):
...          print 'from A'
>>> class B(object):
...      def t(self):
...          print 'from B'
>>> class C(A):
...      pass
>>> class D(C, B):
...      pass
>>> d = D()
>>> d.t()
from A

class D(C, B)替换为class D(B, C):后再尝试相同的操作,找出不同之处。 - Kracekumar
是的,结果是不同的,因为 Python 会首先在类 B 中搜索,并找到返回 from B 的方法 t。在我的示例中,它会先搜索 C,然后在其父级中搜索,在搜索 B 之前进行。 - Joël
我没有看到这里使用super关键字。在这个例子中,如何使用它来实现相同的结果? - Arindam Roychowdhury

1

super() 使用 方法解析顺序(MRO)来确定调用哪个超类方法。

在多继承的情况下控制调用哪个类方法的最佳方法是显式调用它,而不使用super()

或者,您可以重新排列子类定义中的超类以更改 MRO。这允许您继续使用super(),但会牺牲一些可读性,我的看法是这样。

使用 super() 的示例:

class Wolf(C, Animal):
    def eat(self):
        # C.eat() comes first in the MRO
        super(Wolf, self).eat()

对比:

class Wolf(Animal, C):
    def eat(self):
        # Animal.eat() comes first in the MRO
        super(Wolf, self).eat()

这通常也不起作用,因为Wolf的子类可能具有与您预期不同的MRO。 - Mike Graham
@Mike GrahamпјҡиҝҷдёӘдҫӢеӯҗжј”зӨәдәҶеҰӮдҪ•д»…жҺ§еҲ¶Wolfзұ»зҡ„MROпјҢиҖҢдёҚжҳҜеҸҜиғҪзҡ„Wolfеӯҗзұ»зҡ„MROпјҢдҪҶдҪ жҸҗеҮәдәҶдёҖдёӘеҫҲеҘҪзҡ„и§ӮзӮ№гҖӮ - Alex Smith
没错,但是super的存在是为了解决在任意继承情况下仅调用每个超类方法一次的问题。如果我们不担心额外的继承,我们就不需要一开始就要求使用super - Mike Graham
super() 仅调用一个超类方法,而不是每个超类方法。即使没有 super() ,也可以通过显式调用超类方法来完成,但 super() 很好,因为它在继承层次结构上提供了一层抽象。 - Alex Smith
super 调用恰好一个超类方法。当 每个人 都使用 super,除了存在不需要使用的基类之外,它们都会被恰好调用一次,这就是 super 的作用。super 的存在是因为你无法通过显式调用基类方法来获得相同的行为。 - Mike Graham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接