钻石问题-多重继承python-方法只调用一次-但是如何?

pet*_*rov 2 python multiple-inheritance python-3.x

在下面的这个例子中,m类上的方法A只被调用一次。

我知道这是一个功能,这是解决在这种类似菱形的继承场景中A'sm方法将被调用两次(如果它以幼稚的方式实现)的问题的 Pythonic 方法。

这一切都在这里描述:https :
//www.python-course.eu/python3_multiple_inheritance.php

(1) 但是在幕后……他们是如何实现这种行为的,即该类Am方法只被调用一次?!
简单地询问:在执行过程中哪一行被“跳过” - 是 line#1还是 line # 2

有人可以对此有更多的了解吗?
我从来没有认真使用过多重继承,因为我主要用 Java 编程。所以我真的很好奇这里的这个场景,更具体地说是它背后的内部运作。

注意:我只是想大致了解一下它在 Python 中的工作原理,而不是真正了解这里的每一个细节。

(2) 如果我想(在同样的场景中并且出于某种原因)Am方法被调用两次(或N次数取决于D我们有多少基类),同时仍然使用super(). 这可能吗?是否super()支持这种操作方式?

(3) 这只是一些树或DAG访问算法,它们会跟踪哪个类的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()
Run Code Online (Sandbox Code Playgroud)

r.o*_*ook 5

这与方法解析顺序有关,您链接的文章已经提供了一些见解(以及其他文章中的更多信息):

问题是超级函数如何做出决定。它如何决定必须使用哪个类?正如我们已经提到的,它使用所谓的方法解析顺序(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'>]`
Run Code Online (Sandbox Code Playgroud)

注意 MRO 从D> B> C> 的位置A。您认为super()只是调用当前作用域的父类 - 事实并非如此。它通过当前类(即,...)查看对象的类 MRO(即D.mro()),以确定哪个类是解析该方法的下一个类。BC

super()实际使用两个参数,但与零个参数调用一个类里面,它是隐式传递:

另请注意,除了零参数形式外,super()不限于使用内部方法。两个参数形式准确地指定参数并进行适当的引用。零参数形式仅在类定义中起作用,因为编译器会填写必要的详细信息以正确检索正在定义的类,以及访问普通方法的当前实例。

准确地说,在 点上B.m()super()调用实际上转换为:

super(B, x).m()
# because the self being passed at the time is instance of D, which is x
Run Code Online (Sandbox Code Playgroud)

内的这一呼吁做出决议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())
Run Code Online (Sandbox Code Playgroud)

结果是:

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'>]
Run Code Online (Sandbox Code Playgroud)

所以实际上,没有任何东西会被调用两次或跳过。您只是误解了 MRO 根据范围 where super()is从调用解析的想法,而不是从初始 object调用。


这是另一个有趣的小例子,可以更详细地演示 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())
Run Code Online (Sandbox Code Playgroud)

结果:

# 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'>]
Run Code Online (Sandbox Code Playgroud)