人们普遍认为,多重继承是危险的或不好的,这主要是由于编程语言的多重继承机制执行不当,尤其是由于使用不当而引起的,Java甚至不支持多重继承,而C++支持。Python具有复杂且精心设计的多重继承方法。
在类定义中,当一个名为SubClassName的子类从BaseClass1,BaseClass2,BaseClass3这样的父类继承时,具体格式如下所示:
class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...):
pass
显然,父类BaseClass1,BaseClass2,BaseClass3能从其他父类继承,于是我们可以得到一课继承树:
1. 钻石问题
“钻石问题”(有时被称为“致命的死亡钻石”)是一个常用术语,当两个类B和C继承自父类A,而另一个类D继承自B和C时,会产生歧义。如果A中有一个方法“m”被B或C(甚至两者)重写,那么D继承该方法的哪个版本?可能是A、B或C。
第一个钻石问题的配置是这样的:B和C都覆盖A的方法m。代码如下:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
d=D()
d.m()
在本例中,d为D类的实例,调用d.m()方法,输出如下:
m of B called
如果D类头部变为" class D(C,B) “,输出为” m of C called "。
2. MRO
如果方法m被B或C其中一个类重写,例如在C类中被重写:
class A:
def m(self):
print("m of A called")
class B(A):
pass
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
d=D()
d.m()
在python2和python3的输出结果是不同的:
#Python2输出结果
m of A called
#Python3输出结果
m of C called
为了在Python2中拥有与Python3相同的继承行为,每个类都必须从object类继承。上例中的类A不是从object继承的,所以如果我们用python2调用脚本,就会得到一个所谓的旧样式类。与旧样式类相对,新样式类的第一个父类继承自Python根级别的object类,从格式上看,新旧样式类区别如下:
# Old style class
class OldStyleClass:
pass
# New style class
class NewStyleClass(object):
pass
两种声明样式中的方法解析顺序(MRO)不同。旧样式类使用DLR算法,而新样式类使用C3线性化算法(C3 Linearization algorithm)来实现多种继承的方法解析。
旧样式类的多重继承由两个规则控制:深度优先,然后从左到右。在上例中,DLR算法的解析顺序将为D,B,A,C,A,但是,A不能两次出现,因此顺序将为D,B,A,C。如果将A的定义头部更改为" class A(object): ",那么在这两个Python版本中,将得到相同的结果。
C3线性化算法用于消除DLR算法产生的不一致性,具体计算过程我没看,也不懂,所以就不写了。个人总结MRO的排序遵循以下原则:
- 子类优先于父类,子类全部排序完才会继续检查父类
- 如果一个子类继承自多个父类,则父类将按照子类定义中出现的顺序进行排序
通过使用__mro__属性或mro方法,可以显示类的MRO顺序。示例如下:
class A():
def m(self):
print("m of A called")
class B(A):
pass
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
print(D.__mro__)
在python3的环境下执行得到MRO的顺序为:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
3. Super
3.1 前世
将前面的示例稍加改动,保存为test.py文件:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
A.m(self)
class C(A):
def m(self):
print("m of C called")
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B.m(self)
C.m(self)
在命令行创建对象:
>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of A called
m of C called
m of A called
这里A类的方法m被反复调用了两次,如果修改为只调用一次,有两种思路:一种是将B和C类的m方法分情况编写;一种是使用super()函数。第一种方法不符合python的风格,代码如下:
class A:
def m(self):
print("m of A called")
class B(A):
def _m(self):
print("m of B called")
def m(self):
self._m()
A.m(self)
class C(A):
def _m(self):
print("m of C called")
def m(self):
self._m()
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B._m(self)
C._m(self)
A.m(self)
3.2 今生
第二种方法使用super(),很pythonic:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m()
class C(A):
def m(self):
print("m of C called")
super().m()
class D(B,C):
def m(self):
print("m of D called")
super().m()
两种方法输出一致:
>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
上面描述的是super函数的第一个用途,调用父类方法。super函数的第二个用途是:确保父类被正确初始化。当实例使用__init__方法初始化时,经常会使用super函数,示例如下:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
print('A1.__init__')
super().__init__()
print('A2.__init__')
class B(Base):
def __init__(self):
print('B1.__init__')
super().__init__()
print('B2.__init__')
class C(A,B):
def __init__(self):
print('C1.__init__')
super().__init__() # Only one call to super() here
print('C2.__init__')
将代码保存为test.py,创建对象实例,执行结果为:
>>> from test import *
>>> c=C()
C1.__init__
A1.__init__
B1.__init__
Base.__init__
B2.__init__
A2.__init__
C2.__init__
>>> b=B()
B1.__init__
Base.__init__
B2.__init__
>>> a=A()
A1.__init__
Base.__init__
A2.__init__
从结果可以看出,super()的本质是函数,其行为也符合函数嵌套的执行顺序。
查询各类的mro顺序如下:
>>> C.mro()
[<class 'test.C'>, <class 'test.A'>, <class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> B.mro()
[<class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> A.mro()
[<class 'test.A'>, <class 'test.Base'>, <class 'object'>]
>>> Base.mro()
[<class 'test.Base'>, <class 'object'>]
参考文献
https://www.programiz.com/python-programming/methods/built-in/super
https://www.programiz.com/python-programming/multiple-inheritance
https://www.geeksforgeeks.org/method-resolution-order-in-python-inheritance/?ref=rp
https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
https://www.python-course.eu/python3_multiple_inheritance.php