一、类多重继承
Python是支持类中多重继承的,概念虽然容易,但是困难的工作是如果子类调用一个自身没有定义的属性,它是按照何种顺序去到父类寻找呢,尤其是众多父类中有多个都包含该同名属性。
我们知道Python的类分为经典类与新式类。Python2.7之前的版本中可以采用经典类,经典类继承父类的顺序采用深度优先算法,但在Python3之后的版本就只承认新式类了。新式类在python2.2之后的版本中都可以使用,新式类的继承顺序采用C3算法,其继承顺序可以通过查看MRO列表获取。经典类没有__MRO__
和instance.mro()
调用,而新式类有。
经典类中采用深度优先的匹配方法,可能导致在查询继承树中绕过后面的父类(在Python 2中测试,Python默认使用新式类):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Python 2 class D(): def __init__(self): print("class D") class B(D): pass class C(D): def __init__(self): print("class C") class A(B, C): pass |
结果如下:
1 2 |
>>> f = A() class D |
新式类采用C3算法(区别于广度优先的原则)进行搜索,若使用新式类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Python 2 class D(object): def __init__(self): print("class D") class B(D): pass class C(D): def __init__(self): print("class C") class A(B, C): pass |
结果如下:
1 2 |
>>> f = A() class C |
这里为什么类D没有打印出来呢?因为类C跟类D有相同的方法,子类把父类给覆盖了。如果也想打印父类可以使用super方法(下面会介绍super),如下:
1 2 3 4 |
class C(D): def __init__(self): print("class C") super(C, self).__init__() |
此时再去初始化类A,结果如下:
1 2 3 |
>>> A = A() class C class D |
经典类和新式类各自搜索的顺序如下图所示:
C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
- 本地优先级:指声明时父类的顺序,比如A(B,C),如果访问A类对象属性时,应该根据声明顺序,优先查找B类,然后再查找C类。
- 单调性:如果在A的解析顺序中,B排在C的前面,那么在A的所有子类里,也必须满足这个顺序。
对于下面这一段程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class A(object): def __init__(self): print("A") class B(A): def __init__(self): print("B") class C(B): def __init__(self): print("C") class D(A): def __init__(self): print("D") class E(D): def __init__(self): print("E") class F(C, E): def __init__(self): print("F") |
当初始化实例F = F()时,使用深度优先搜索,广度优先搜索及C3算法的不同搜索顺序如下:
对于新式类,可以用instance.__mro__
或instance.mro()
来查看其MRO(Method Resolution Order 方法解析顺序)列表。对于上文代码中的类F
的MRO如下:
1 2 |
>>> print(F.mro()) [<class '__main__.F'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>] |
结果即C3算法的解析结果。C3线性化算法我们就不去深究了(太深入),感兴趣的读者可以自己去了解一下,总的来说,一个类的MRO列表就是合并所有父类的 MRO 列表,并遵循以下三条原则:
- 子类永远在父类前面。
- 如果有多个父类,会根据它们在列表中的顺序被检查。
- 如果对下一个类存在两个合法的选择,选择第一个父类。
同时为了解决多重继承中子类和父类有重复方法名的问题,Python 2.2之后引入了super函数。
二、super()使用
在类的继承中,如果重定义某个方法时,该方法会覆盖父类的同名方法。如下:
1 2 3 4 5 6 7 |
class Base(): def __init__(self): self.x = 2 class Sub(Base): def __init__(self): self.y = self.x * 2 |
初始化类时就会报错x变量找不到:
1 2 3 4 5 |
>>> A = Sub() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in __init__ AttributeError: 'Sub' object has no attribute 'x' |
当然这个问题你可以通过直接引用父类方法名(Base.__init__(self)),或者子类不使用相同的方法名来解决,但是都不够友好,并且绝对引用修改起来也是灾难性的。
因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python 2.3的官方文档说明:
1 2 3 4 5 6 7 8 9 10 11 12 |
super(type[, object-or-type]) Return the superclass of type. If the second argument is omitted the super object returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true. super() only works for new-style classes. A typical use for calling a cooperative superclass method is: class C(B): def meth(self, arg): super(C, self).meth(arg) |
从说明来看super只能工作在新式类下,其继承object。从示例来看,我们可以把Sub子类改一下,如下:
1 2 3 4 5 6 7 8 |
class Base(): def __init__(self): self.x = 2 class Sub(Base): def __init__(self): super(Sub, self).__init__() # Python 3可以写成super().__init__(); self.y = self.x * 2 |
super()函数的一个常见用法是在__init__()方法中确保父类被正确的初始化。
这回初始化Sub类就没有问题了,如下:
1 2 3 |
>>> A = Sub() >>> A.y 4 |
可以看到使用super后把代码的维护量降到最低,是一个不错的用法。另外在Python 3里语法有所改变:你可以用super().__init__()替换super(Sub, self).__init__(),更简洁,很Nice。
对于super(Sub, self).__init__()是这样理解的:super(Sub, self)首先找到Sub的父类(就是类Base),然后把类Sub的对象self转换为类Base的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类Base对象调用自己的__init__函数。
看了上面的使用,你可能会觉得super的使用很简单,无非就是获取了父类,并调用父类的方法。其实,在上面的情况下,super获得的类刚好是父类,但在其他情况就不一定了,super其实和父类没有实质性的关联。
如下,让我们看一个稍微复杂的例子,涉及到多重继承。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class D(object): def __init__(self): print("class D") class B(D): def __init__(self): print("class B") super(B, self).__init__() print("class F") class C(D): def __init__(self): print("class C") super(C, self).__init__() print("class G") class A(B, C): def __init__(self): print("class A") super(A, self).__init__() print("class E") |
其中,D是父类,B、C继承自D,A继承自B、C;它们的继承关系如下:
1 2 3 4 5 6 7 |
D / \ / \ B C \ / \ / A |
现在,让我们看一下结果:
1 2 3 4 5 6 7 8 |
>>> A = A() class A class B class C class D class G class F class E |
按照我们理想的结果显示出来了(使用新式类)。
我们看一下A的MRO(Method Resolution Order)方法的解析顺序,如下:
1 2 |
>>> print(A.mro()) [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>] |
从这个例子可以看出,super和父类没有实质性的关联。下面让我们搞清super是怎么运作的。
三、super原理
super的工作原理如下:
1 2 3 |
def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1] |
其中,cls 代表类,inst 代表实例,上面的代码做了两件事:
- 获取 inst 的 MRO 列表
- 查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1]
当你使用 super(cls, inst) 时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。
现在,让我们回到前面的例子。
首先看类A的__init__方法:
1 |
super(A, self).__init__() |
这里的self是当前A的实例,self.__class__.mro()结果是:
1 |
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>] |
可以看到,A的下一个类是B,于是,跳到了B的__init__,这时会打印出B,并执行下面一行代码:
1 |
super(B, self).__init__() |
注意,这里的self也是当前A的实例,MRO列表跟上面是一样的,搜索B在MRO中的下一个类,发现是C,于是,跳到了C的__init__,这时会打印出class C。
整个过程还是比较清晰的,关键是要理解super的工作方式,而不是想当然地认为super调用了父类的方法。通过上面的类继承讲解,知道主要是新式类的类搜索顺序发生了改变;所以super是依赖新式类,不然无法工作。
<参考>