多重继承调用顺序

5 投票
2 回答
3116 浏览
提问于 2025-04-17 09:48
 A      B
| |   |   |
C D   E   F
| |   |   |
    G     H
      |
      I


user@ubuntu:~/Documents/Python/oop_python$ cat tt.py
class A:
    def call_me(self):
        print("A")

class C(A):
    def call_me(self):
        super().call_me()
        print("C")

class D(A):
    def call_me(self):
        super().call_me()
        print("D")

class B:
    def call_me(self):
        print("B")

class E(B):
    def call_me(self):
        super().call_me()
        print("E")

class F(B):
    def call_me(self):
        super().call_me()
        print("F")

class G(C, D, E):
    def call_me(self):
        super().call_me()
        print("G")

class H(F):
    def call_me(self):
        super().call_me()
        print("H")

class I(G, H):
    def call_me(self):
        super().call_me()
        print("I")


user@ubuntu:~/Documents/Python/oop_python$ python3.2 -i tt.py
>>> i = I()
>>> i.call_me()
A
D
C
G
I
//            updated based on comments from delnan

user@ubuntu:~/Documents/Python/oop_python$ cat tt.py
class BaseClass():
    def call_me(self):
        print("BaseClass")
    pass

class A(BaseClass):
    def call_me(self):
        super().call_me()
        print("A")

class C(A):
    def call_me(self):
        super().call_me()
        print("C")

class D(A):
    def call_me(self):
        super().call_me()
        print("D")

class B(BaseClass):
    def call_me(self):
        super().call_me()
        print("B")

class E(B):
    def call_me(self):
        super().call_me()
        print("E")

class F(B):
    def call_me(self):
        super().call_me()
        print("F")

class G(C, D, E):
    def call_me(self):
        super().call_me()
        print("G")

class H(F):
    def call_me(self):
        super().call_me()
        print("H")

class I(G, H):
    def call_me(self):
        super().call_me()
        print("I")

user@ubuntu:~/Documents/Python/oop_python$ python3.2 -i tt.py
>>> i = I()
>>> i.call_me()
BaseClass
B
F
H
E
A
D
C
G
I

问题> 为什么 BEF 没有被打印出来?

2 个回答

1

使用super的前提是这个类必须是新式类

在Python中,super有两个使用场景:

  1. 第一个场景是单继承的类层次结构,super可以用来引用父类,而不需要明确地写出父类的名字。

  2. 第二个场景是支持在动态执行环境中的协作多继承。

所以,第二个场景符合你提到的第二个例子。在这种情况下,子类会进行一个广度优先遍历,确保每个重写的基类方法只被调用一次。你的继承树可以按照这个顺序(遵循广度优先遍历从左到右)重写为:I G C D A E H F B BaseClass。

super方法的定义是:

Super(type[,object-or-type])

它会返回一个代理对象,这个对象会把方法调用转发给指定类型的父类或兄弟类。在Python 3.x中,你可以直接调用super(),这和super(currentClassName, self)是一样的。这会获取通过广度优先遍历生成的直接右侧的父类或兄弟类的代理。所以方法i.call_me()的调用顺序是:

I.call_me -> G.call_me -> C.call_me -> D.call_me -> A.call_me -> E.call_me -> H.call_me -> F.call_me -> B.call_me -> BaseClass.call_me

两个例子的区别

你问为什么第一个例子没有打印B、E、F,因为继承图不是“钻石图”,这意味着A.call_me和B.call_me是不同的方法。所以Python只选择其中一个继承分支。

希望这些对你有帮助。你也可以查看Python类文档获取更多细节。

7

一个常见的误解是,super() 会调用所有父类的方法。其实并不是这样,它只会调用其中一个。具体调用哪个,是由 super() 根据一些特定的规则自动决定的。有时候,它调用的甚至不是父类,而是兄弟类。不过,除非所有的类都使用 super(),否则不能保证所有的方法都会被调用。

在这个例子中,A 和 B 并没有调用 super。如果你在 A 中添加 super,它会调用那些“缺失”的类,但如果你在 B 中添加 super,就会出错,因为在这个特定的情况下,B 会成为“最后一个”(或者说第一个,这取决于你的看法)类。

如果你想使用 super(),最好的办法是为 A 和 B 创建一个共同的基类,这个基类实现了 call_me 方法,但不调用 super()。(感谢 delnan 提出的建议)。

不过,如果你了解你的类层次结构,你可以直接调用父类的方法,而不是使用 super()。需要注意的是,在上面的例子中,这并不意味着每个类都必须直接调用它的每个父类。这在你作为程序员无法完全控制类层次结构的情况下并不实用,比如当你编写库或混合类时。这时候你就得使用 super()。

撰写回答