Chapter 12 - 继承的优缺点

12.1 子类化内置类型很麻烦

使用 C 语言实现的内置类型不会调用用户顶以的类覆盖的特殊方法,如 dict 的子类覆盖的 __getitem__() 方法不会被内置类型的 get() 方法调用。

具体我们可以看看下面的例子:


In [1]: class DoppelDict(dict): 
   ...:     def __setitem__(self, key, value): 
   ...:         super().__setitem__(key, [value] * 2) 
   ...:                                                                                   

In [2]: dd = DoppelDict(one=1)                                                            

In [3]: dd                                                                                
Out[3]: {'one': 1}

In [4]: dd['two'] = 2                                                                     

In [5]: dd                                                                                
Out[5]: {'one': 1, 'two': [2, 2]}

In [6]: dd.update(three=3)                                                                

In [7]: dd                                                                                
Out[7]: {'one': 1, 'two': [2, 2], 'three': 3}

所以,直接实例化内置类型(如 dictliststr)容易出错,用户自己定义的类,应该继承 collections 模块中的类,如 UserDictUserListUserString,这样就不会出现内置类型不调用子类覆盖的特殊方法的尴尬场面。

12.2 多重继承和方法的解析顺序

Python 会按照特定的顺序遍历继承图来决定如何解析子类调用的方法,这个顺序叫做方法解析顺序(Method Resolution Order)。每个类都有一个名为 __mro__ 的属性,它的值是一个元组,按照方法解析顺序列出该类的各个超类,同时,MRO 还与类声明超类的顺序有关。

12.4 处理多重继承

  1. 把接口继承和实现继承区分开:继承接口是为了实现“是什么”的关系,继承实现是为了重用代码
  2. 使用抽象基类表示接口
  3. 通过混入重用代码:如果一个类的作用是为多个不相关的子类提供方法实现,从而实现重用,但不体现“是什么”关系,应该把那个类明确地定义为混入类(mixin class)。从概念上讲,混入不定义新类型,只是打包方法,便于重用。
  4. 在名称中明确指明混入:混用类的类名强烈建议使用 Mixin 作为后缀。
  5. 抽象基类可以作为混入,反过来则不成立
  6. 不要子类化多个具体类:具体类的超类中除了一个具体超类之外,其余的应该都是抽象基类或混入
  7. 为用户提供聚合类:如果抽象基类或混入的组合对客户代码非常有用,那就提供一个类,使用易于理解的方式把它们结合起来。
  8. 优先使用对象组合而不是类继承