Почему super() вызывает метод в обоих суперклассах?
Такой код:
class BaseClass:
def call_me(self):
print("Calling method on Base Class")
class LeftSubclass(BaseClass):
def call_me(self):
super().call_me()
print("Calling method on Left Subclass")
class RightSubclass(BaseClass):
def call_me(self):
super().call_me()
print("Calling method on Right Subclass")
class SubClass(LeftSubclass, RightSubclass):
def call_me(self):
super().call_me()
print("Calling method on SubClass")
s = SubClass()
s.call_me()
Результат такой
Calling method on Base Class
Calling method on Right Subclass
Calling method on Left Subclass
Calling method on SubClass
[Finished in 0.1s]
Получается, что SubClass.call_me() всегда вызывает указанный метод у всех суперклассов (при его наличии)? А как же правило лишь о первом найденном совпадении? Разве SubClass.call_me() не должен вызвать лишь первый найденный у своих суперклассов метод, считая слева направо (здесь: LeftSubClass)
К тому же непонятен порядок печати. Получается такой порядок исполнения кода, из дебаггера
https://www.onlinegdb.com/online_python_debugger:
16 SubClass.call_me()
17 SubClass.super().call_me() # вызывает call_me() первого слева суперкласса LestSubClass
6 LeftSubClass.call_me()
# тут странность: на строке 7 вызывается не сall_me() суперкласса
# LeftSubClass - BaseClass, а RightSubClass.call_me()
7 LeftSubClass.super().call_me() # вызван RightSubClass.call_me()
11 RightSubClass.call_me() # RightSubClass вызывает сall_me() у BaseClass
12 BaseClass.call_me() # print("Calling method on Base Class")
# тут начались странности:
# print("Calling method on Right Subclass")
# print("Calling method on Left Subclass")
# print("Calling method on SubClass")
Получается, что на строке 7 вызывается не сall_me() суперкласса LeftSubClass (BaseClass), а RightSubClass.call_me(). Почему так? И почему такой порядок принтов?
вот что написано в книжке:
First call_me of Subclass calls super().call_me() , which happens to refer to LeftSubclass.call_me(). LeftSubclass.call_me() then calls super().call_me(), but in this case, super() is referring to RightSubclass.call_me(). Pay particular attention to this; the super call is not calling the method on the superclass of LeftSubclass (which is BaseClass), it is calling RightSubclass, even though it is not a parent of LeftSubclass! This is the next method, not the parent method. RightSubclass then calls BaseClass and the super calls have ensured each method in the class hierarchy is executed once.
Кто понмиает\знает, почему тут:
1. "the super call is not calling the method on the superclass of LeftSubclass (which is BaseClass), it is calling RightSubclass, even though it is not a parent of LeftSubclass!"?
2. почему "LeftSubclass.call_me() then calls super().call_me(), but in this case, super() is referring to RightSubclass.call_me()"?
3. что такое "the next method"?
Ответы (1 шт):
Правило о первом найденном совпадении работает в вашем примере, когда вы вызываете метод call_me() в SubClass. И если вы не будете вызывать super().call_me(), то на этом действие прекратится. Метод же super().call_me() передает выполнение следующему родителю по порядку наследования (mro) для вызова соответствующего метода.
Получается, что на строке 7 вызывается не сall_me() суперкласса LeftSubClass (BaseClass), а RightSubClass.call_me(). Почему так? И почему такой порядок принтов?
В python2 порядок наследования определялся через поиск родителей, который велся вверх во всех родительских классах на максимальную глубину.
В python 3 этот порядок несколько изменился и ведется по алгоритму c3. Если кратко, то
Сначала в список добавляются родители объекта
в конец списка добавляет список родителей этих родителей и так далее
если какой-то класс оказывается в списке дважды, то оставляется только последнее его вхождение
То есть в вашем случае для класса
class SubClass(LeftSubclass, RightSubclass):
при вызове super() внутри него ищется первый родитель слева направо. Это LeftSubclass, затем RightSubclass. Затем ищутся родители этих классов, и добавляются в конец. Т.к. они совпадают (BaseClass), то остаётся только он один.
В итоге стэк вызовов выглядит так:
SubClass=>LeftSubclass=>RightSubclass=>BaseClass
Что и показал дебаггер