[扩展阅读] 多重继承的陷阱:钻石继承(菱形继承)问题

多重继承的陷阱:钻石继承(菱形继承)问题

支持多继承的面向对象编程都可能会导致钻石继承(菱形继承)问题,看以下代码:

class A():
    def __init__(self):
        print("进入A…")
        print("离开A…")

class B(A):
    def __init__(self):
        print("进入B…")
        A.__init__(self)
        print("离开B…")
        
class C(A):
    def __init__(self):
        print("进入C…")
        A.__init__(self)
        print("离开C…")

class D(B, C):
    def __init__(self):
        print("进入D…")
        B.__init__(self)
        C.__init__(self)
        print("离开D…")

----------------------------------------------------------------------------------------
d = D()
========================= RESTART: D:/python/fish.py =========================
>>> d = D()
进入D…
进入B…
进入A…
离开A…
离开B…
进入C…
进入A…
离开A…
离开C…
离开D…

为什么叫钻石继承(菱形继承),看下图就明白名字的由来了:

钻石继承(菱形继承)会带来什么问题?

多重继承容易导致钻石继承(菱形继承)问题,上边代码实例化 D 类后我们发现 A 被前后进入了两次(有童鞋说两次就两次憋,我女朋友还不止呢……)。

这有什么危害?我举个例子,假设 A 的初始化方法里有一个计数器,那这样 D 一实例化,A 的计数器就跑两次(如果遭遇多个钻石结构重叠还要更多),很明显是不符合程序设计的初衷的(程序应该可控,而不能受到继承关系影响)。

如何避免钻石继承(菱形继承)问题?

为解决这个问题,Python 使用了一个叫“方法解析顺序(Method Resolution Order,MRO)”的东西,还用了一个叫 C3 的算法。

该算法相对来说比较复杂(有兴趣深入算法的朋友可以阅读:https://www.python.org/download/releases/2.3/mro

当然我这里愿意跟你解释下 MRO 的顺序基本就是:在避免同一类被调用多次的前提下,使用广度优先和从左到右的原则去寻找需要的属性和方法。

在继承体系中,C3 算法确保同一个类只会被搜寻一次。例子中,如果一个属性或方法在 D 类中没有被找到,Python 就会搜寻 B 类,然后搜索 C类,如果都没有找到,会继续搜索 B 的基类 A,如果还是没有找到,则抛出“AttributeError”异常。

你可以使用 类名.__mro__ 获得 MRO 的顺序(注:object 是所有类的基类,金字塔的顶端):

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

其实你大可不必为上边的内容而烦恼,因为这时候你应该召唤 super 函数大显神威:

class A():
    def __init__(self):
        print("进入A…")
        print("离开A…")

class B(A):
    def __init__(self):
        print("进入B…")
        super().__init__()
        print("离开B…")
        
class C(A):
    def __init__(self):
        print("进入C…")
        super().__init__()
        print("离开C…")

class D(B, C):
    def __init__(self):
        print("进入D…")
        super().__init__()
        print("离开D…")

>>> d = D()
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…

 

没有更多推荐了,返回首页