python - python 菱形继承(钻石问题)中的 super() 奇怪行为

标签 python python-3.x inheritance super diamond-problem

如以下示例所示,super() 在菱形继承(钻石问题)中使用时有一些奇怪的行为(至少对我而言)。

class Vehicle:
    def start(self):
        print("engine has been started")

class LandVehicle(Vehicle):
    def start(self):
        super().start()
        print("tires are safe to use")

class WaterCraft(Vehicle):
    def start(self):
        super().start()
        print("anchor has been pulled up")

class Amphibian(LandVehicle, WaterCraft):
    def start(self):
        # we do not want to call WaterCraft.start, amphibious
        # vehicles don't have anchors
        LandVehicle.start(self)
        print("amphibian is ready for travelling on land")

amphibian = Amphibian()
amphibian.start()

以上代码产生以下输出:

engine has been started
anchor has been pulled up
tires are safe to use
amphibian is ready for travelling on land

当我调用 super().some_method() 时,我绝不会期望调用同一继承级别上的类的方法。所以在我的示例中,我不希望 anchor has been pulled up 出现在输出中。

调用 super() 的类可能甚至不知道最终调用其方法的其他类。在我的示例中,LandVehicle 可能甚至不知道 WaterCraft

这种行为是否正常/预期,如果是,其背后的基本原理是什么?

最佳答案

当您在 Python 中使用继承时,每个类都会定义一个方法解析顺序 (MRO),用于决定在查找类属性时查找的位置。为您Amphibian类,例如,MRO 是 Amphibian , LandVehicle , WaterCraft , Vehicle最后 object . (您可以通过调用 Amphibian.mro() 亲自查看。)

MRO 派生方式的具体细节有点复杂(不过,如果您有兴趣,可以找到有关其工作原理的描述)。需要知道的重要一点是,任何子类总是列在其父类之前,如果进行多重继承,子类的所有父类将按照与 class 中相同的相对顺序排列。声明(其他类可能出现在父类之间,但它们永远不会相对于彼此反转)。

当您使用 super 时调用覆盖的方法时,它看起来虽然 MRO 就像它对任何属性查找所做的那样,但它比平常更进一步地开始搜索。具体来说,它会在“当前”类之后开始搜索属性。 “当前”是指包含 super 的方法的类调用(即使调用该方法的对象属于其他派生类)。所以当LandVehicle.__init__电话 super().__init__ , 它开始检查 __init__ LandVehicle 之后第一个类中的方法在 MRO 中,找到 WaterCraft.__init__ .

这为您提供了一种解决问题的方法。你可以有 Amphibian姓名WaterCraft作为它的第一个基类,LandVehicle第二:

class Amphibian(Watercraft, LandVehicle):
    ...

改变碱基的顺序也会改变它们在 MRO 中的顺序。当Amphibian.__init__电话 LandVehicle.__init__直接按名称(而不是使用 super ),随后的 super调用将跳过 WaterCraft因为他们被调用的类(class)已经在 MRO 中更进一步了。因此 super 的其余部分调用将按您的预期工作。

但这并不是一个很好的解决方案。当你明确地命名一个基类时,如果你有更多的子类想要以不同的方式做事,你可能会发现它稍后会破坏事情。例如,从重新排序的基础 Amphibian 派生的类以上可能会以 WaterCraft 之间的其他基类结束。和 LandVehcle , 这也将有他们的 __init__ Amphibian.__init__ 时意外跳过了方法电话 LandVehcle.__init__直接。

更好的解决方案是允许所有 __init__依次调用的方法,但为了分解出它们的一部分,您可能不希望总是遇到可以单独覆盖的其他方法。

例如,您可以更改 WaterCraft到:

class WaterCraft(Vehicle):
    def start(self):
        super().start()
        self.weigh_anchor()

    def weigh_anchor(self):
        print("anchor has been pulled up")

Amphibian类可以覆盖 anchor 特定行为(例如什么也不做):

class Amphibian(LandVehicle, WaterCraft):
    def start(self):
        super().start(self)
        print("amphibian is ready for travelling on land")

    def weigh_anchor(self):
        pass # no anchor to weigh, so do nothing

当然,在这种特定情况下,WaterCraft除了提升它的 anchor 之外什么都不做,删除 WaterCraft 会更简单作为 Amphibian 的基类.但同样的想法通常适用于重要的代码。

关于python - python 菱形继承(钻石问题)中的 super() 奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52526451/

相关文章:

python - 尽管出现 UnicodeDecodeError 错误,我如何继续处理文件?

python - Google 相册 API - 新版本?

Python: "FOR"循环打印基于行的列字段匹配

ios - NSCoding 的 Swift 通用方法

c++ - 在保留原始方法的同时扩展基类

python - 删除除 BeautifulSoup 的一个标签之外的所有 html 标签

python - 根据条件替换所有列中的所有值

python - Elasticsearch必须具有多个条件

javascript - Javascript中的封装与继承

python - 通过 Python 从给定的 URL 获取加载的图像