本章重点说明子类化内置类型的缺点,多重继承和方法解析顺序
子类化内置类型#
python2.2
之后,内置类型可以子类化了,但是内置类型(使用c语言编写)不会调用用户定义的类覆盖方法(即重写“Override”
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class MyDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key, [value]*2)
d = MyDict({'one':1, 'two':2})
print(d)
>>>{'one': 1, 'two': 2}
d['three'] = 3
print(d)
>>>{'one': 1, 'two': 2, 'three': [3, 3]}
d.update(four=4)
print(d)
>>>{'one': 1, 'two': 2, 'three': [3, 3], 'four': 4}
|
上述的例子的意思是,继承自内置类型的两个方法a()
和b()
,b()
方法中调用了a()
,我们重写了a()
,此时b()
仍然调用的是原来的a()
, 而不是我们重写后的a()
原生类型的这种行为违背了面向对象编程的一个基本原则:始终应该从实例(self)所属的类开始搜索方法,即使在超类实现的类中调用也是如此。
这样的问题只发生在C语言实现的内置类型内部的方法委托上,解决这个问题的方法是,使用collections.UserDict
, collections.UserList
, collections.UserString
多重继承和方法解析顺序#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class A:
def ping(self):
print('ping: ', self)
class B(A):
def pong(self):
print('pong from B: ', self)
class C(A):
def pong(self):
print('pong from C: ', self)
class D(B, C):
def ping(self):
super().ping()
print('post-ping: ', self)
def pingpong(self):
self.ping()
super().ping()
self.pong()
super().pong()
C.pong(self)
print(D.__mro__)
>>> (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
d = D()
d.pong()
>>> pong from B: <__main__.D object at 0x7f13109ff438>
d.ping()
>>> ping: <__main__.D object at 0x7fb439350438>
>>> post-ping: <__main__.D object at 0x7fb439350438>
d.pingpong()
>>> ping: <__main__.D object at 0x7f0890d65470>
>>> post-ping: <__main__.D object at 0x7f0890d65470>
>>> ping: <__main__.D object at 0x7f0890d65470>
>>> pong from B: <__main__.D object at 0x7f0890d65470>
>>> pong from B: <__main__.D object at 0x7f0890d65470>
>>> pong from C: <__main__.D object at 0x7f0890d65470>
|
- 可以看到
d.pong()
调用的是B.pong()
- 注意
ping()
里的self
- 注意
d.pong()
调用的是B.pong()
, 方法解析顺序不仅考虑继承图,还考虑子类声明中列出超类的顺序
self
实例一致,(访问__mro__
可以知道方法解析顺序)(Method Resolution Order,MRO)
- 支持像
c++
一样, 使用类名加方法名访问, 注意必须显式传入self
参数,因为这样访问的是未绑定方法(unbound method)。
- 方法解析顺序使用
C3
算法计算。Michele Simionato
的论文The Python 2.3 Method Resolution Order
(https://www.python.org/download/releases/2.3/mro/)对Python方法解析顺序使用的 C3 算法做了权威论述
处理多重继承#
(1)把接口继承和实现继承区分开#
使用多重继承时一定要明确一开始为什么创建子类,原因可能有:
- 继承接口,创建子类型,实现“是什么”关系
- 继承实现,通过重用避免代码重复
(2)使用抽象基类表示接口#
- 如果类的作用是定义接口,应该明确把它定义为抽象基类(即使用
abc.ABC
)
(3)通过混入Mixin
重用代码#
- 如果一个类的作用是为
多个不相关
的子类提供方法实现,从而实现重用,但不体现“是什么(is a
)”关系,应该把那个类明确地定义为混入类(mixin class)
- 混入类绝对不能实例化,而且具体类不能只继承混入类。混入类应该提供某方面的特定行为,只实现少量关系非常紧密的方法。
- 在名称中明确指明混入,例如
MyClassMixin
- 抽象基类可以作为混入
(4)不要子类化多个具体类#
具体类可以没有,或最多只有一个具体超类。也就是说,具体类的超类中除了这一个具体超类之外,其余的都是抽象基类或混入。例如,在下述代码中,如果Alpha
是具体类,那么Beta
和Gamma
必须是抽象基类或混入:
1
2
|
class MyConcreteClass(Alpha, Beta, Gamma):
pass
|
ps:这一点没有get到作者的点, 是不是有点太
(5)使用组合#