Когда вы используете наследование в 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
name WaterCraft
в качестве первого базового класса, а LandVehicle
second:
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
.Но та же самая идея часто может работать для нетривиального кода.