钻石问题 - 多重继承 python - 只调用一次的方法 - 但是如何呢?
Diamond problem - multiple inheritance python - method called just once - but how?
在下面的示例中,class A
上的方法 m
仅被调用一次。
我知道这是一个功能,这是解决 A
的 m
方法被调用两次的问题的 Pythonic 方法(如果它在天真的方式)在这种钻石般的继承场景中。
这就是全部描述:
https://www.python-course.eu/python3_multiple_inheritance.php
(1) 但在幕后......他们是如何实现这种行为的,即 class A
的 m
方法只被调用一次?!
简单地问:执行期间哪一行是 "skipped" - 是行 #1
还是行 # 2
?
有人可以对此进行更多说明吗?
我从来没有认真地使用过多重继承,因为我主要在 Java 中编程。所以我真的很好奇这里的这种情况,更具体地说是它背后的内部运作。
注意:我只是想大致了解它在 Python 中的工作原理,而不是真正了解这里的每一个细节。
(2) 如果我想(在同样的情况下出于某种原因)A
的 m
方法被调用两次怎么办 (或 N
次,这取决于我们有多少 D
的碱基 class],同时仍在使用 super()
。这可能吗? super()
支持这样的操作方式吗?
(3) 这只是一些树还是 DAG 访问算法,他们跟踪哪个 class 的 m
方法已经被访问过,只是不访问它(叫它)两次?如果是这样,那么 简单地说 我猜 '# 2' 是被跳过的行。
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m() # 1
class C(A):
def m(self):
print("m of C called")
super().m() # 2
class D(B,C):
def m(self):
print("m of D called")
super().m()
if (__name__ == '__main__'):
x = D()
x.m()
这与 方法解析顺序 有关,您链接的文章已经提供了一些见解(以及来自 this other article as well 的更多信息):
The question arises how the super functions makes its decision. How
does it decide which class has to be used? As we have already
mentioned, it uses the so-called method resolution order(MRO). It is
based on the C3 superclass linearisation algorithm. This is called a
linearisation, because the tree structure is broken down into a linear
order. The mro method can be used to create this list:
>>> 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()
只是调用当前作用域的父 class 的地方 - 它不是。它正在查看您的对象的class MRO(即D.mro()
)和当前class(即B
、C
...) 以确定下一个 class 行来解析该方法。
super()
实际上使用了两个参数,但是当在 class 中使用零参数 调用时,它被隐式传递:
Also note that, aside from the zero argument form, super()
is not
limited to use inside methods. The two argument form specifies the
arguments exactly and makes the appropriate references. The zero
argument form only works inside a class definition, as the compiler
fills in the necessary details to correctly retrieve the class being
defined, as well as accessing the current instance for ordinary
methods.
准确地说,在 B.m()
点,super()
调用实际上转换为:
super(B, x).m()
# because the self being passed at the time is instance of D, which is x
从 B
class 开始,该调用在 D.mro()
内解析,实际上是 C
,而不是您想象的 A
。因此,首先调用 C.m()
,在其中,super(C, x).m()
解析为 A.m()
并调用。
之后解析回[=32=内的super()
后,回溯到[=26=内的super()
后,回溯到[=39] =].当您添加更多行时,很容易观察到这一点:
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'>]
在下面的示例中,class A
上的方法 m
仅被调用一次。
我知道这是一个功能,这是解决 A
的 m
方法被调用两次的问题的 Pythonic 方法(如果它在天真的方式)在这种钻石般的继承场景中。
这就是全部描述:
https://www.python-course.eu/python3_multiple_inheritance.php
(1) 但在幕后......他们是如何实现这种行为的,即 class A
的 m
方法只被调用一次?!
简单地问:执行期间哪一行是 "skipped" - 是行 #1
还是行 # 2
?
有人可以对此进行更多说明吗?
我从来没有认真地使用过多重继承,因为我主要在 Java 中编程。所以我真的很好奇这里的这种情况,更具体地说是它背后的内部运作。
注意:我只是想大致了解它在 Python 中的工作原理,而不是真正了解这里的每一个细节。
(2) 如果我想(在同样的情况下出于某种原因)A
的 m
方法被调用两次怎么办 (或 N
次,这取决于我们有多少 D
的碱基 class],同时仍在使用 super()
。这可能吗? super()
支持这样的操作方式吗?
(3) 这只是一些树还是 DAG 访问算法,他们跟踪哪个 class 的 m
方法已经被访问过,只是不访问它(叫它)两次?如果是这样,那么 简单地说 我猜 '# 2' 是被跳过的行。
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m() # 1
class C(A):
def m(self):
print("m of C called")
super().m() # 2
class D(B,C):
def m(self):
print("m of D called")
super().m()
if (__name__ == '__main__'):
x = D()
x.m()
这与 方法解析顺序 有关,您链接的文章已经提供了一些见解(以及来自 this other article as well 的更多信息):
The question arises how the super functions makes its decision. How does it decide which class has to be used? As we have already mentioned, it uses the so-called method resolution order(MRO). It is based on the C3 superclass linearisation algorithm. This is called a linearisation, because the tree structure is broken down into a linear order. The mro method can be used to create this list:
>>> 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()
只是调用当前作用域的父 class 的地方 - 它不是。它正在查看您的对象的class MRO(即D.mro()
)和当前class(即B
、C
...) 以确定下一个 class 行来解析该方法。
super()
实际上使用了两个参数,但是当在 class 中使用零参数 调用时,它被隐式传递:
Also note that, aside from the zero argument form,
super()
is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.
准确地说,在 B.m()
点,super()
调用实际上转换为:
super(B, x).m()
# because the self being passed at the time is instance of D, which is x
从 B
class 开始,该调用在 D.mro()
内解析,实际上是 C
,而不是您想象的 A
。因此,首先调用 C.m()
,在其中,super(C, x).m()
解析为 A.m()
并调用。
之后解析回[=32=内的super()
后,回溯到[=26=内的super()
后,回溯到[=39] =].当您添加更多行时,很容易观察到这一点:
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'>]