Это связано с Порядком разрешения метода , о котором статья, на которую вы ссылались, уже дала некоторое представление (и дополнительную информацию из этой другой статьи, а также ):
Возникает вопрос, как суперфункции принимают решение. Как он решает, какой класс следует использовать? Как мы уже упоминали, он использует так называемый порядок разрешения методов (MRO). Он основан на алгоритме C3 линеаризации суперкласса . Это называется линеаризацией, потому что древовидная структура разбита на линейный порядок. Для создания этого списка можно использовать метод mro:
>>> from super_init import A,B,C,D`
>>> D.mro() [<class 'super_init.D'>, <class 'super_init.B'>, <class 'super_init.C'>, <class 'super_init.A'>, <class 'object'>]`
Обратите внимание на MRO, откуда он идет от D
> B
> C
> A
. Если вы полагаете, что super()
просто вызывает родительский класс текущей области - это не так. Он просматривает класс MRO вашего объекта (то есть D.mro()
) с текущим классом (то есть B
, C
...), чтобы определить, какой следующий класс в строке разрешит Метод.
super()
фактически использует два аргумента, но при вызове с нулевыми аргументами внутри класса он неявно передается:
Также обратите внимание, что, кроме формы с нулевым аргументом, super()
не ограничивается использованием внутренних методов. Форма с двумя аргументами точно определяет аргументы и делает соответствующие ссылки. Форма с нулевым аргументом работает только внутри определения класса, так как компилятор заполняет необходимые детали для правильного извлечения определяемого класса, а также доступа к текущему экземпляру для обычных методов.
Чтобы быть точным в точке B.m()
вызов super()
фактически преобразуется в:
super(B, x).m()
# because the self being passed at the time is instance of D, which is x
Этот вызов разрешается в пределах D.mro()
начиная с класса B
, который фактически равен C
, не A
, как вы себе представляли. Следовательно, сначала вызывается C.m()
, а внутри него super(C, x).m()
разрешается до A.m()
, и это называется.
После этого он разрешается обратно после super()
в пределах C.m()
, резервное копирование до super()
в пределах B.m()
и резервное копирование до D.m()
. Это легко заметить, если добавить еще несколько строк:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
print(super())
super().m() # resolves to C.m
print('B.m is complete')
class C(A):
def m(self):
print("m of C called")
print(super())
super().m() # resolves to A.m
print('C.m is complete')
class D(B,C):
def m(self):
print("m of D called")
print(super())
super().m() # resolves to B.m
print('D.m is complete')
if (__name__ == '__main__'):
x = D()
x.m()
print(D.mro())
Что приводит к:
m of D called
<super: <class 'D'>, <D object>>
m of B called
<super: <class 'B'>, <D object>>
m of C called
<super: <class 'C'>, <D object>>
m of A called
C.m is complete # <-- notice how C.m is completed before B.m
B.m is complete
D.m is complete
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
Таким образом, в действительности ничего никогда не вызывается дважды или пропускаются. Вы просто неверно истолковали идею разрешения MRO из вызова на основе области, в которой находится super()
, в отличие от вызова из исходного объекта .
Вот еще одна забава Небольшой пример для демонстрации MRO более подробно:
def print_cur_mro(cls, obj):
# helper function to show current MRO
print(f"Current MRO: {' > '.join([f'*{m.__name__}*' if m.__name__ == cls.__name__ else m.__name__ for m in type(obj).mro()])}")
class X:
def m(self):
print('m of X called')
print_cur_mro(X, self)
try:
super().a_only() # Resolves to A.a_only if called from D(), even though A is not in X inheritance
except AttributeError as exc:
# Resolves to AttributeError if not called from D()
print(type(exc), exc)
print('X.m is complete')
class A:
def m(self):
print("m of A called")
print_cur_mro(A, self)
def a_only(self):
print('a_only called')
class B(X):
def m(self):
print("m of B called")
print_cur_mro(B, self)
super().m() # Resolves to X.m
print('B.m is complete')
def b_only(self):
print('b_only called')
class C(A):
def m(self):
print("m of C called")
print_cur_mro(C, self)
try:
super().b_only() # Resolves to AttributeError if called, since A.b_only doesn't exist if from D()
except AttributeError as exc:
print(type(exc), exc)
super().m() # Resolves to A.m
print('C.m is complete')
def c_only(self):
print('c_only called, calling m of C')
C.m(self)
class D(B,C):
def m(self):
print("m of D called")
print_cur_mro(D, self)
super().c_only() # Resolves to C.c_only, since c_only doesn't exist in B or X.
super().m() # Resolves to B.m
print('D.m is complete')
if (__name__ == '__main__'):
x = D()
x.m()
print(D.mro())
x2 = X()
x2.m()
print(X.mro())
Результат:
# x.m() call:
m of D called
Current MRO: *D* > B > X > C > A > object
c_only called, calling m of C
m of C called
Current MRO: D > B > X > *C* > A > object
<class 'AttributeError'> 'super' object has no attribute 'b_only'
m of A called
Current MRO: D > B > X > C > *A* > object
C.m is complete
m of B called
Current MRO: D > *B* > X > C > A > object
m of X called
Current MRO: D > B > *X* > C > A > object
a_only called
X.m is complete
B.m is complete
D.m is complete
# D.mro() call:
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.X'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# x2.m() call:
m of X called
Current MRO: *X* > object
<class 'AttributeError'> 'super' object has no attribute 'a_only'
X.m is complete
# X.mro() call:
[<class '__main__.X'>, <class 'object'>]