gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 15:40:57 25 4
gpt4 key购买 nike

如以下示例所示,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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com