多重继承的陷阱:钻石继承(菱形继承)问题
支持多继承的面向对象编程都可能会导致钻石继承(菱形继承)问题,看以下代码:
- 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()
- 进入D…
- 进入B…
- 进入A…
- 离开A…
- 离开B…
- 进入C…
- 进入A…
- 离开A…
- 离开C…
- 离开D…
为什么叫钻石继承(菱形继承),看下图就明白名字的由来了:
<ignore_js_op>
钻石继承(菱形继承)会带来什么问题?
多重继承容易导致钻石继承(菱形继承)问题,上边代码实例化 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…
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步