前面介绍的大部分的继承都是单继承,既一个子类只有一个父类,但是Python也支持多重继承,即一个子类可以有多个父类。多继承有复杂的父类冲突问题,大部分的面向对象语言都仅仅支持单继承,Python是为数不多支持多继承的语言,本文对此展开学习。
多继承的语法结构
多继承的语法结构一般如下:
class SubClassName(BaseClass1,BaseClass2,…):
def __init__(self, *args):
…
当然,子类所继承的所有父类同样也能有自己的父类,这样就可以得到一个继承关系机构图如下图所示:
父类也许很复杂,在多继承中比较难处理的是菱形的继承结构:
我们用实例来分析多继承的情况:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
pass
g = Grand()
g.foo()
‘’'
Base1.__init__
Base2.__init__
Base.__init__
Base1.foo
‘''
从上面的例子可以看到:
- 孙对象的__init__方法调用了两个父对象的__init__方法,但是只调用了一次祖父对象的__init__方法
- 孙对象的foo方法只调用了第一个父对象的foo方法,未调用第二个父对象和祖父对象的foo方法。
甚至可以跨代混合继承:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base):
pass
g = Grand()
g.foo()
‘’’
Base1.__init__
Base.__init__
Base1.foo
’‘’
但是也有禁区,解释器无法区分应该使用哪个父类方法的场景:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base, Base2):
pass
g = Grand()
g.foo()
‘’’
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Base, Base2
’‘’
MRO
MRO(Method Resolution Order)也叫方法解析顺序。可以通过type().mro()查询类的解析顺序。
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
class Base3(Base):
def __init__(self):
print(f'Base3.__init__')
super().__init__()
def foo(self):
print(f'Base3.foo')
class Base31(Base1, Base2):
pass
class Base32(Base2, Base3):
pass
class Grand(Base32, Base31):
pass
g = Grand()
g.foo()
print(Grand.mro())
‘’’
Base1.__init__
Base2.__init__
Base3.__init__
Base.__init__
Base1.foo
[<class '__main__.Grand'>, <class '__main__.Base32'>, <class '__main__.Base31'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base3'>, <class '__main__.Base'>, <class 'object'>]
’‘’
对于只支持单继承的编程语言来说,MRO 很简单,就是从当前类开始,逐个搜索它的父类。对于多继承,MRO 相对会复杂一些。Python 发展至今,经历了以下 3 种 MRO 算法,分别是:
- 从左往右,采用深度优先搜索(DFS)的算法,称为旧式类的 MRO;
- 自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化;
- 自 Python 2.3 版本,对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法。
旧式类MRO算法
在使用旧式类的 MRO 算法时,以下面代码为例:
class A:
def method(self):
print("CommonA")
class B(A):
pass
class C(A):
def method(self):
print("CommonC")
class D(B, C):
pass
D().method()
通过分析可以想到,此程序中的 4 个类是一个“菱形”继承的关系,当使用 D 类对象访问 method() 方法时,根据深度优先算法,搜索顺序为D->B->A->C->A
。
使用旧式类的 MRO 算法最先搜索得到的是基类 A 中的 method() 方法,即在 Python 2.x 版本中,此程序的运行结果为:
CommonA
但是,这个结果显然不是想要的,我们希望搜索到的是 C 类中的 method() 方法。
新式类MRO算法
为解决旧式类 MRO 算法存在的问题,Python 2.2 版本推出了新的计算新式类 MRO 的方法,它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。
仍以上面程序为例,通过深度优先遍历,其搜索顺序为D->B->A->C->A
,由于此顺序中有 2 个 A,因此仅保留后一个,简化后得到最终的搜索顺序为D->B->C->A
。
新式类可以直接通过 类名.__mro__
的方式获取类的 MRO,也可以通过 类名.mro()
的形式,旧式类是没有 __mro__
属性和mro()
方法的。
可以看到,这种 MRO 方式已经能够解决“菱形”继承的问题,但是可能会违反单调性原则。所谓单调性原则,是指在类存在多继承时,子类不能改变基类的 MRO 搜索顺序,否则会导致程序发生异常。
例如:
class X(object):
pass
class Y(object):
pass
class A(X,Y):
pass
class B(Y,X):
pass
class C(A, B):
pass
通过进行深度遍历,得到搜索顺序为C->A->X->object->Y->object->B->Y->object->X->object
,再进行简化(相同取后者),得到C->A->B->Y->X->object
下面来分析这样的搜索顺序是否合理,我们来看下各个类中的 MRO:
- 对于 A,其搜索顺序为 A->X->Y->object;
- 对于 B,其搜索顺序为 B->Y->X->object;
- 对于 C,其搜索顺序为 C->A->B->X->Y->object。
可以看到,B 和 C 中,X、Y 的搜索顺序是相反的,也就是说,当 B 被继承时,它本身的搜索顺序发生了改变,这违反了单调性原则。
MRO C3
为解决 Python 2.2 中 MRO 所存在的问题,Python 2.3 采用了 C3 方法来确定方法解析顺序。多数情况下,如果某人提到 Python 中的 MRO,指的都是 C3 算法。C3算法解决了单调性问题和只能继承无法重写问题,具体的MRO C3算法比较复杂(The Python 2.3 Method Resolution Order | Python.org),就不在这里叙述了。
super()
Python 的内置函数 super() 用于调用父类(超类)的一个方法,用来解决多重继承问题的。不仅仅可以调用父类的构造函数,还可以调用父类的成员函数。
super() 内置函数(对象)返回一个代理对象(超类的临时对象),允许我们访问基类的方法。
class Person(object):
def eat(self, times):
print(f'我每天吃{times}餐。')
class Student(Person):
def eat(self):
# 调用超类
super().eat(4)
tom = Student()
tom.eat()
# 我每天吃4餐。
Student.mro()
# [__main__.Student, __main__.Person, object]
调用父类初始化继承:
class Base(object):
def __init__(self, a, b):
self.a = a
self.b = b
class A(Base):
def __init__(self, a, b, c):
super().__init__(a, b)
#super(A, self).__init__(a, b) # Python2 写法
self.c = c
a = A(1,2,3)
A.mro()
# [__main__.A, __main__.Base, object]
语法
它其实是一个内置的类,语法如下:
class super(self, /, *args, **kwargs)
# or
super(type[, object-or-type])
返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。 这对于访问已在类中被重载的继承方法很有用。但特别注意,调用的是type指定的父类或兄弟类,具体调用哪一个类,取决于MRO的顺序,也就是说,实际上是调用的MRO列表上,type指定的类的下一个类,看下面的例子:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
def __init__(self):
print(f'Grand.__init__')
super().__init__()
def foo(self):
print(f'Grand.foo')
super(Base1, self).foo()
g = Grand()
g.foo()
print(Grand.mro())
‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
‘''
foo()函数打印的结果让你震惊,因为你写的是
super(Base1, self).foo()
但是实际输出的是
Base2.foo
而Base2与Base1其实根本就没有直接的关系,怎么会调用到Base2的foo了呢?
是因为super()根本就不看Base1的继承关系,而是看Grand的MRO,可以看到,Grand的MRO的顺序上,Base2是Base1的下一个类,而super(Base1,self).foo()调用的正是Base1下一个类的foo()方法。
注意:如果省略第二个参数,则返回的超类对象是未绑定的。 如果第二个参数为一个对象,则
isinstance(obj, type)
必须为真值。 如果第二个参数为一个类型,则issubclass(type2, type)
必须为真值(这适用于类方法)。
除了方法查找之外,super() 也可用于属性查找。 一个可能的应用场合是在上级或同级类中调用 描述器(任何定义了 __get__()
, __set__()
或 __delete__()
方法的对象)。
请注意 super() 是作为显式加点属性查找的绑定过程的一部分来实现的,例如 super().__getitem__(name)。 它做到这一点是通过实现自己的 __getattribute__() 方法,这样就能以可预测的顺序搜索类,并且支持协作多重继承。 对应地,super() 在像 super()[name] 这样使用语句或操作符进行隐式查找时则未被定义。
还要注意的是,除了零个参数的形式以外,super() 并不限于在方法内部使用。 两个参数的形式明确指定参数并进行相应的引用。 零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
self.__nicename__ = 'Base2'
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
def __init__(self):
print(f'Grand.__init__')
super().__init__()
def foo(self):
print(f'Grand.foo')
super(Base1, self).foo()
print('__getattribute__:', super().__getattribute__('__nicename__'))
g = Grand()
g.foo()
print(Grand.mro())
‘’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
__getattribute__: Base2
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
‘''