《流畅的Python》第十二章学习笔记

子类化内置类型

在Python2.2之前,内置类型不能子类化

内置类型不会掉用用户定义的类覆盖的特殊方法。


例子1:内置类型dict的`
init__`和` _update_
`方法会忽略我们覆盖的`__setitem

`方法

class DoppelDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key, [value] * 2)
if __name__ == '__main__':
d = DoppelDict(one=1)
print(d)
d['two'] = 2
print(d)
d.update(three=3)
print(d)

输出内容

{'one': 1}
{'one': 1, 'two': [2, 2]}
{'one': 1, 'two': [2, 2], 'three': 3}

从上面的代码中可以看到,我们继承内置类型 dict
并重写了它的 __setitem__
方法,但是只有使用 d['two'] = 2
才生效了

:warning:原生类型的这种行为 违背
了面向对象编程的一个基本原则:始终应该从实例「self」所属的类开始搜索方法,即使在超类实现的类中调用也是如此。


例子2:dict.update方法会忽略`AnswerDict. getitem
`方法

class AnswerDict(dict):
def __getitem__(self, item):
return 42
if __name__ == '__main__':
ad = AnswerDict(a='foo')
print(ad['a'])
d = {}
d.update(ad)
print(d)

输出内容

42
{'a': 'foo'}

内置类型的方法调用其他类的方法,如果被覆盖了,也不会被调用。

小结

直接子类化内置类型容易出错,因为内置类型的方法通常会忽略用户覆盖的方法。

不要子类化内置类型,用户自己定义的类应该继承 collectiions
模块中的类。

例如 UserDict
UserList
UserString
,这些类做了特殊设计,因此易于扩展。

import collections
class DoppelDict2(collections.UserDict):
def __setitem__(self, key, value):
super().__setitem__(key, [value] * 2)
class AnswerDict2(collections.UserDict):
def __getitem__(self, item):
return 42
if __name__ == '__main__':
d = DoppelDict2(one=1)
print(d)  # {'one': [1, 1]}
d['two'] = 2
print(d)  # {'one': [1, 1], 'two': [2, 2]}
d.update(three=3)
print(d)  # {'one': [1, 1], 'two': [2, 2], 'three': [3, 3]}
ad = AnswerDict2(a='foo')
print(ad['a'])  # 42
d = {}
d.update(ad)
print(d)  # {'a': 42}

多重继承和方法解析顺序

菱形问题:任何实现多重继承的语言都要处理潜在的命名冲突,这种冲突由不相关的祖先类实现同名方法引起。

菱形问题

上图的方法解析顺序为:D->B->C->A

Python会按照特定的顺序遍历继承图。这个顺序叫方法解析顺序「Method Resolution Order,MRO」

类都有一个名为 __mro__
的属性,它的值是一个元祖,按照方法解析顺序列出各个超类,从当前类一直向上,直到object类。

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

若想把方法调用委托给超类

  • 在python3中使用 super().ping()
    函数

  • 在python2中使用 super(D,self).ping()

  • 绕过方法解析顺序,直接调用某个超类的方法「这样做有时更方便」 A.ping(self)

:warning:直接在类上调用实例方法,必须显式传入 self
参数,因为这样访问的是未绑定方法

处理多重继承的建议

把接口继承和实现继承区分开

创建子类的原因:

  • 继承接口,创建子类型,实现「是什么」关系

  • 继承实现,通过重用避免代码重复

接口继承是框架的支柱。

继承重用代码是实现细节,通常可以换用组合和委托模式

使用抽象基类显式表示接口

创建abc.ABC或其他抽象基类的子类

通过混入重用代码

混入类绝对不能实例化,而且具体类不能只继承混入类。

混入类应该提供某方面的特定行为,只实现少量关系非常紧密的方法。

在名称中明确指明混入

混入即把两个对象或者类的内容,混合起来,从而实现一些功能的复用。

因为在Python中没有把类声明为混入的正规方法,所以强烈推荐在名称中加入 Mixin
后缀

抽象基类可以作为混入,反过来则不成立

抽象基类可以实现具体方法,所以也可以作为混入使用。

抽象基类中实现的方法只能与抽象基类及其超类中的方法协作。这表明,抽象基类中的具体方法只是一种便利措施,因为这些方法所做的一切,用户调用抽象基类中的其他方法也能做到。

不要子类化多个具体类

具体类的超类中除了这一个具体超类之外,其余的都是抽象基类或者混入。

为用户提供聚合类

一个空的类继承了多个类,方便用户直接使用,例如Django的ListView

优先使用对象组合,而不是类继承

测试游记
我还没有学会写个人说明!
上一篇

奥康携手有赞实现自我突破,24区域门店业绩显著提升

下一篇

Hooks 邂逅 MobX ,代码变得更丝滑了!

你也可能喜欢

评论已经被关闭。

插入图片