如以下示例所示,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
。
此行为是否正常/是否正常?如果是,那么背后的原因是什么?
答案 0 :(得分:3)
在Python中使用继承时,每个类都定义一个“方法解析顺序”(MRO),该方法用于决定在查找类属性时应在哪里查找。例如,对于您的Amphibian
类,MRO为Amphibian
,LandVehicle
,WaterCraft
,Vehicle
,最后是object
。 (您可以通过致电Amphibian.mro()
亲自查看。)
有关MRO派生方式的确切细节有些复杂(尽管您可以在感兴趣的地方找到其运作方式的描述)。要知道的重要一点是,任何子类总是在其父类之前列出,并且如果正在进行多重继承,则子类的所有父类将以与class
语句中相同的相对顺序排列(其他班级可能会出现在父母之间,但它们永远不会相互颠倒)。
当您使用super
调用重写的方法时,它看起来像MRO一样,适用于任何属性查找,但是它比平时更进一步地开始搜索。具体来说,它开始在“当前”类之后搜索属性。我所说的“当前”是指包含super
调用方法的类(即使正在调用该方法的对象是其他派生类的另一种)。因此,当LandVehicle.__init__
调用super().__init__
时,它将开始检查MRO中__init__
之后的第一类中的LandVehicle
方法,并找到WaterCraft.__init__
。>
这表明您可以解决此问题的一种方法。您可以将Amphibian
名称WaterCraft
作为其第一基类,并将LandVehicle
作为其第二基类:
class Amphibian(Watercraft, LandVehicle):
...
更改碱基的顺序也将更改其在MRO中的顺序。当Amphibian.__init__
直接通过名称(而不是使用LandVehicle.__init__
来调用super
时,后续的super
调用将跳过WaterCraft
,因为它们被称为在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
类可以覆盖特定于锚的行为(例如不执行任何操作):
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
除了提高锚点以外不做任何其他事情,将WaterCraft
删除为Amphibian
的基类甚至更简单。但是,同样的想法通常也适用于非平凡的代码。