Python类的多继承以及super()机制

Python支持类的多继承,通过super()方法实现对不同父类的访问。

MRO

MRO 即 Method Resolution Order(方法解析顺序)。在调用方法时,会对当前类及其所有基类进行搜索,找到需要调用的方法,而 MRO 规定了这一搜索顺序

例如,定义以下类继承结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Origin:
pass

class BaseA(Origin):
pass

class A(BaseA):
pass

class B(Origin):
pass

class Current(A, B):
pass

可以通过方法 mro() 查看类 Current 的 MRO:

1
2
3
4
5
6
print(Current.mro())

# Output:
# [<class '__main__.Current'>, <class '__main__.A'>,
# <class '__main__.BaseA'>, <class '__main__.B'>,
# <class '__main__.Origin'>, <class 'object'>]

可以发现,MRO 列表第一个元素即当前类,最后一个元素是 object 类,同时满足:

  • MRO 中类顺序保持定义时父类顺序
  • 所有子类在父类之前

这里 MRO 列表中 BaseA 类位于 B 类之前,可以推测 MRO 默认是深度优先的。

之后的 super() 方法的父类查找机制也是建立在 MRO 顺序之上的。

super()

先给出结论:super() 方法用于访问参数对象所属类的 MRO 中,位于参数类之后的

这里有两个需要解释的点:

  • 如果是多继承,super() 访问的是一个还是多个类?

  • 提到了参数,但哪里有参数?

首先,super() 仅访问一个类,但不一定是父类,也可能是兄弟类,取决于嵌套调用时最初对象所属类的 MRO。那么最初对象又是什么?🤔 这里就到了要解释的第二个点。

先对前面定义的类做一些修改,具体看一下 super() 访问的机制。给每个类添加一个方法,打印并调用 super() 类中的方法(由于 Origin 的父类时 object 类,没有调用方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Origin:
def test(self):
print(super())

class BaseA(Origin):
def test(self):
print(super())
super().test()

class A(BaseA):
def test(self):
print(super())
super().test()

class B(Origin):
def test(self):
print(super())
super().test()

class Current(A, B):
def test(self):
print(super())
super().test()

执行

1
2
3
4
5
6
7
8
9
testObj = Current()
testObj.test()

# output:
# <super: <class 'Current'>, <Current object>>
# <super: <class 'A'>, <Current object>>
# <super: <class 'BaseA'>, <Current object>>
# <super: <class 'B'>, <Current object>>
# <super: <class 'Origin'>, <Current object>>

这里并没有直接输出访问的类,但是可以看到每次调用都提供给 super() 方法两个参数。程序首先通过对象 testObj 调用 test() 方法,即 Current 类定义的 test() 方法。根据第一行输出,super() 方法中传入的是 Current 类,以及 Current 对象作为参数。之后嵌套调用 super() 类中的 test() 方法,根据第二行输出,可见访问了 A 类,在这里 super() 方法传入的是 A 类,以及 Current 对象作为参数。之后继续嵌套,又访问了 BaseA,B,以及 Origin 类。😶(其实通过 Origin 类中的 super() 最后访问到了 object 类,但并没有在 object 类中输出)

可以发现:向 super() 传递的第一个参数即调用 super() 方法所在的类,而第二个参数保持不变,是嵌套调用最开始的对象。而访问类的顺序和最初的类,即嵌套调用最开始对象所属类的 MRO 一致。

在 python2 版本,super() 方法的参数还不可省略:

1
super(cls, obj)
  • cls: 当前类
  • obj: 嵌套调用时的最初对象

实际上,super() 方法的访问逻辑如下:

1
2
3
4
def super(cls, obj):
mro_list = obj.__class__.mro()
next_parent_class = mro_list[mro_list.index(cls)+1]
return next_parent_class

即:访问 obj 所属类的 MRO 列表中,cls 的下一个类

实用

super() 访问机制已经解释清楚了,但是这样的机制有什么好处呢?在实际应用中,super() 一般被用在初始化方法 __init__() 中,而这种嵌套调用对参数的要求必然十分严格,如何处理这种情况?

continue...

参考