《Effective Python 编写高质量Python代码的59个有效方法》读书笔记

具体的59条

第 1 章 用 Pythonic 方式来思考 1

第 1 条:确认自己所用的 Python 版本 1
第 2 条:遵循 PEP 8 风格指南 3
第 3 条:了解 bytes、 str 与 unicode 的区别 5
第 4 条:用辅助函数来取代复杂的表达式 8

第 5 条:了解切割序列的办法 10
第 6 条:在单次切片操作内,不要同时指定 start、 end 和 stride 13
第 7 条:用列表推导来取代 map 和 f ilter 15
第 8 条:不要使用含有两个以上表达式的列表推导 16
第 9 条:用生成器表达式来改写数据量较大的列表推导 18
第 10 条:尽量用 enumerate 取代 range 20
第 11 条:用 zip 函数同时遍历两个迭代器 21
第 12 条:不要在 for 和 while 循环后面写 else 块 23
第 13 条:合理利用 try/except/else/f inally 结构中的每个代码块 25
第 2 章 函数 28
第 14 条:尽量用异常来表示特殊情况,而不要返回 None 28
第 15 条:了解如何在闭包里使用外围作用域中的变量 30
第 16 条:考虑用生成器来改写直接返回列表的函数 35
第 17 条:在参数上面迭代时,要多加小心 37
第 18 条:用数量可变的位置参数减少视觉杂讯 41
第 19 条:用关键字参数来表达可选的行为 43
第 20 条:用 None 和文档字符串来描述具有动态默认值的参数 46
第 21 条:用只能以关键字形式指定的参数来确保代码明晰 49
第 3 章 类与继承 53
第 22 条:尽量用辅助类来维护程序的状态,而不要用字典和元组 53
第 23 条:简单的接口应该接受函数,而不是类的实例 58
第 24 条:以 @classmethod 形式的多态去通用地构建对象 62
第 25 条:用 super 初始化父类 67
第 26 条:只在使用 Mix-in 组件制作工具类时进行多重继承 71
第 27 条:多用 public 属性,少用 private 属性 75
第 28 条:继承 collections.abc 以实现自定义的容器类型 79
第 4 章 元类及属性 84
第 29 条:用纯属性取代 get 和 set 方法 84
第 30 条:考虑用 @property 来代替属性重构 88
第 31 条:用描述符来改写需要复用的 @property 方法 92
第 32 条:用 __getattr__、 __getattribute__ 和 __setattr__ 实现按需生成的属性 97
第 33 条:用元类来验证子类 102
第 34 条:用元类来注册子类 104
第 35 条:用元类来注解类的属性 108
第 5 章 并发及并行 112
第 36 条:用 subprocess 模块来管理子进程 113
第 37 条:可以用线程来执行阻塞式 I/O,但不要用它做平行计算 117
第 38 条:在线程中使用 Lock 来防止数据竞争 121XIV
第 39 条:用 Queue 来协调各线程之间的工作 124
第 40 条:考虑用协程来并发地运行多个函数 131
第 41 条:考虑用 concurrent.futures 来实现真正的平行计算 141
第 6 章 内置模块 145
第 42 条:用 functools.wraps 定义函数修饰器 145
第 43 条:考虑以 contextlib 和 with 语句来改写可复用的 try/f inally 代码 148
第 44 条:用 copyreg 实现可靠的 pickle 操作 151
第 45 条:应该用 datetime 模块来处理本地时间,而不是用 time 模块 157
第 46 条:使用内置算法与数据结构 161
第 47 条:在重视精确度的场合,应该使用 decimal 166
第 48 条:学会安装由 Python 开发者社区所构建的模块 168
第 7 章 协作开发 170
第 49 条:为每个函数、类和模块编写文档字符串 170
第 50 条:用包来安排模块,并提供稳固的 API 174
第 51 条:为自编的模块定义根异常,以便将调用者与 API 相隔离 179
第 52 条:用适当的方式打破循环依赖关系 182
第 53 条:用虚拟环境隔离项目,并重建其依赖关系 187
第 8 章 部署 193
第 54 条:考虑用模块级别的代码来配置不同的部署环境 193
第 55 条:通过 repr 字符串来输出调试信息 195
第 56 条:用 unittest 来测试全部代码 198
第 57 条:考虑用 pdb 实现交互调试 201
第 58 条:先分析性能,然后再优化 203
第 59 条:用 tracemalloc 来掌握内存的使用及泄漏情况 208


第 1 条:确认自己所用的 Python 版本 1

1.有两个版本的 Python 处于活跃状态,它们是: Python 2 与 Python 3。
2.有很多种流行的 Python 运行时环境,例如, CPython、 Jython、 IronPython 以及
PyPy 等。
 3.在操作系统的命令行中运行 Python 时,请确保该 Python 的版本与你想使用的
Python 版本相符。
 4.由于 Python 社区把开发重点放在 Python 3 上,所以在开发后续项目时,应该优
先考虑采用 Python 3。


第 2 条:遵循 PEP 8 风格指南 3

空白: Python 中的空白(whitespace)会影响代码的含义。 Python 程序员使用空白
的时候尤其在意,因为它们还会影响代码的清晰程度。
‰‰ 使用 space(空格)来表示缩进,而不要用 tab(制表符)。
‰‰ 和语法相关的每一层缩进都用 4 个空格来表示。
‰‰ 每行的字符数不应超过 79。
‰‰ 对于占据多行的长表达式来说,除了首行之外的其余各行都应该在通常的缩进
级别之上再加 4 个空格。
‰‰ 文件中的函数与类之间应该用两个空行隔开。
‰‰ 在同一个类中,各方法之间应该用一个空行隔开。
‰‰ 在使用下标来获取列表元素、调用函数或给关键字参数赋值的时候,不要在两
旁添加空格。
‰‰ 为变量赋值的时候,赋值符号的左侧和右侧应该各自写上一个空格,而且只写
一个就好。
命名: PEP 8 提倡采用不同的命名风格来编写 Python 代码中的各个部分,以便在阅
读代码时可以根据这些名称看出它们在 Python 语言中的角色。
‰‰ 函数、变量及属性应该用小写字母来拼写,各单词之间以下划线相连,例如,
lowercase_underscore

‰‰ 受保护的实例属性,应该以单个下划线开头,例如,_leading_underscore
‰‰ 私有的实例属性,应该以两个下划线开头,例如,__double_leading_underscore
‰‰ 类与异常,应该以每个单词首字母均大写的形式来命名,例如,CapitalizedWord
‰‰ 模块级别的常量,应该全部采用大写字母来拼写,各单词之间以下划线相连,
例如,
ALL_CAPS
‰‰ 类中的实例方法( instance method),应该把首个参数命名为self,以表示该对象自身。
‰‰ 类方法( class method)的首个参数,应该命名为cls,以表示该类自身。
表达式和语句The Zen of Python》(Python之禅)中说:“每件事都应该有直白的做
法,而且最好只有一种。”
PEP 8 在制定表达式和语句的风格时,就试着体现了这种思想。
‰‰ 采用内联形式的否定词,而不要把否定词放在整个表达式的前面,例如,应该
if a is not b 而不是if not a is b
‰‰ 不要通过检测长度的办法(如 if len(somelist) == 0)来判断somelist是否为 []
'' 等空值,而是应该采用if not somelist这种写法来判断,它会假定:空值将
自动评估为
False
‰‰ 检测 somelist是否为[1] 'hi'等非空值时,也应如此, if somelist语句默认会把
非空的值判断为
True
‰‰ 不要编写单行的 if 语句、 for循环、while 循环及 except 复合语句,而是应该把
这些语句分成多行来书写,以示清晰。
‰‰ import 语句应该总是放在文件开头。
‰‰ 引入模块的时候,总是应该使用绝对名称,而不应该根据当前模块的路径来
使用相对名称。例如,引入
bar 包中的foo模块时,应该完整地写出 from bar
import foo
,而不应该简写为 import foo
‰‰ 如果一定要以相对名称来编写 import 语句,那就采用明确的写法: from.import foo
‰‰ 文件中的那些 import 语句应该按顺序划分成三个部分,分别表示标准库模块、
第三方模块以及自用模块。在每一部分之中,各
import 语句应该按模块的字母
顺序来排列


要点
‰‰ 当编写 Python代码时,总是应该遵循PEP 8 风格指南。
‰‰ 与广大 Python开发者采用同一套代码风格,可以使项目更利于多人协作。
‰‰ 采用一致的风格来编写代码,可以令后续的修改工作变得更为容易。

第 3 条:了解 bytes、 str 与 unicode 的区别


‰‰ 在 Python 3 中, bytes 是一种包含 8 位值的序列, str 是一种包含 Unicode 字符的
序列。开发者不能以 > 或 + 等操作符来混同操作 bytes 和 str 实例。
‰‰ 在 Python 2 中, str 是一种包含 8 位值的序列, unicode 是一种包含 Unicode 字符
的序列。如果 str 只含有 7 位 ASCII 字符,那么可以通过相关的操作符来同时使
用 str 与 unicode。
‰‰ 在对输入的数据进行操作之前,使用辅助函数来保证字符序列的类型与开发者
的期望相符(有的时候,开发者想操作以 UTF-8 格式来编码的 8 位值,有的时
候,则想操作 Unicode 字符)。 


第 4 条:用辅助函数来取代复杂的表达式 8

‰‰ 开发者很容易过度运用Python的语法特性,从而写出那种特别复杂并且难以理
解的单行表达式。
‰‰ 请把复杂的表达式移入辅助函数之中,如果要反复使用相同的逻辑,那就更应
该这么做。
‰‰ 使用 if/else表达式,要比用or and这样的Boolean 操作符写成的表达式更加
清晰


第 5 条:了解切割序列的办法 10

切割操作的基本写法是 somelist[start:end],其中start(起始索引)所指的元素涵盖
在切割后的范围内,而
end(结束索引)所指的元素则不包括在切割结果之中
如果从列表开头获取切片,那就不要在 start 那里写上 0,而是应该把它留空,这样
代码看起来会清爽一些。

如果切片一直要取到列表末尾,那就应该把 end 留空,因为即便写了,也是多余。
在指定切片起止索引时,若要从列表尾部向前算,则可使用负值来表示相关偏移
量。如果采用下面这些写法来切割列表,那么即便是刚刚接触代码的人也能立刻明白程
序的意图。由于这些写法都不会令人惊讶,所以笔者推荐大家在代码中放心地使用。
 

请注意,如果使用负变量作为 start索引来切割列表,那么在极个别情况下,可
能会导致奇怪的结果。例如,
somelist[-n:] 这个表达式,在n大于 1时可以正常
运作 ,如当
n 3 时, somelist[-3:]的结果是正常的。然而,当n 0时,表
达式
somelist[-0:] 则成了原列表的一份拷贝。
‰‰ 不要写多余的代码:当 start索引为0,或end索引为序列长度时,应该将其省略。
‰‰ 切片操作不会计较 start end索引是否越界,这使得我们很容易就能从序列的
前端或后端开始,对其进行范围固定的切片操作(如
a[:20] a[-20:])。
‰‰ list赋值的时候,如果使用切片操作,就会把原列表中处在相关范围内的值替
换成新值,即便它们的长度不同也依然可以替换。


第 6 条:在单次切片操作内,不要同时指定 start、 end 和 stride 13

除了基本的切片操作(参见本书第 5条)之外,Python还提供了 somelist[start:end:stride]
形式的写法,以实现步进式切割,也就是从每 n个元素里面取1 个出来。例如,可以指
定步进值(
stride),把列表中位于偶数索引处和奇数索引处的元素分成两组: 

问题在于,采用 stride方式进行切片时,经常会出现不符合预期的结果。例如,
Python 中有一种常见的技巧,能够把以字节形式存储的字符串反转过来,这个技巧就是
采用 -
1 做步进值。
这种技巧对字节串和 ASCII 字符有用,但是对已经编码成 UTF-8 字节串的 Unicode
字符来说,则无法奏效

我们不应该把 stridestart end写在一起。如果非要用
stride,那就尽量采用正值,同时省略startend索引。如果一定要配合start end
引来使用
stride,那么请考虑先做步进式切片,把切割结果赋给某个变量,然后在那个
变量上面做第二次切割。

上面这种先做步进切割,再做范围切割的办法 ,会多产生一份原数据的浅拷贝。
执行第一次切割操作时,应该尽量缩减切割后的列表尺寸。如果你所开发的程序对执行
时间或内存用量的要求非常严格,以致不能采用两阶段切割法,那就请考虑
Python
置的
itertools 模块。该模块中有个islide方法,这个方法不允许为startendstride
定负值(参见本书第
46 条)。 8
‰‰ 既有 startend,又有stride的切割操作,可能会令人费解。
‰‰ 尽量使用 stride为正数,且不带start end索引的切割操作。尽量避免用负数
stride
‰‰ 在同一个切片操作内,不要同时使用startendstride。如果确实需要执行这
种操作,那就考虑将其拆解为两条赋值语句,其中一条做范围切割,另一条做
步进切割,或考虑使用内置
itertools 模块中的islice


第 7 条:用列表推导来取代 map 和 filter 15

Python 提供了一种精练的写法,可以根据一份列表来制作另外一份。这种表达式
称为
list comprehension(列表推导)。例如,要用列表中每个元素的平方值构建另一份
列表。如果采用列表推导来实现,那就同时指定制作新列表时所要迭代的输入序列,以
及计算新列表中每个元素的值时所用的表达式。
 

除非是调用只有一个参数的函数,否则,对于简单的情况来说,列表推导要比内置
map 函数更清晰。如果使用 map,那就要创建 lambda函数,以便计算新列表中各个
元素的值,这会使代码看起来有些乱


列表推导则不像 map 那么复杂,它可以直接过滤原列表中的元素,使得生成的新
列表不会包含对应的计算结果。例如,在计算平方值时,我们只想计算那些可以为
2
整除的数。如果采用列表推导来做,那么只需在循环后面添加条件表达式即可:
 

A = [1,2,3,3,42,4,5]
b = [x**3 for x in A]

A = [1,2,3,3,42,4,5]
b = [x**3 for x in A]
c = [x**3 for x in A if x%2 == 0]
print(b)
print(c)

[1, 8, 27, 27, 74088, 64, 125]
[8, 74088, 64]


f = {"a":1,"bb":2,"cbb":3}
g = {key: value for value, key in f.items()}
h = {len(num) for num in g.values()}
print(g)
print(h)
{1: 'a', 2: 'bb', 3: 'cbb'}
{1, 2, 3}
‰‰ 列表推导要比内置的 map f ilter 函数清晰,因为它无需额外编写 lambda
达式。
‰‰ 列表推导可以跳过输入列表中的某些元素,如果改用 map 来做,那就必须辅以
f ilter 方能实现。
‰‰ 字典与集也支持推导表达式。


第 8 条:不要使用含有两个以上表达式的列表推导 16

在列表推导中,最好不要使用两个以上的表达式。可以使用两个条件、两个循环或
一个条件搭配一个循环。如果要写的代码比这还复杂,那就应该使用普通的
if for
句,并编写辅助函数(参见本书第
16 条)。 

‰‰ 列表推导支持多级循环,每一级循环也支持多项条件。
‰‰ 超过两个表达式的列表推导是很难理解的,应该尽量避免。


第 9 条:用生成器表达式来改写数据量较大的列表推导 18

列表推导(参见本书第 7 条)的缺点是:在推导过程中,对于输入序列中的每个值
来说,可能都要创建仅含一项元素的全新列表。当输入的数据比较少时,不会出问题,
但如果输入的数据非常多,那么可能会消耗大量内存,并导致程序崩溃。
例如,要读取一份文件并返回每行的字符数。若采用列表推导来做,则需把文件每
一行的长度都保存在内存中。如果这个文件特别大,或是通过无休止的
network socket
(网络套接字)来读取,那么这种列表推导就会出问题。下面的这段列表推导代码,只适
合处理少量的输入值


为了解决此问题, Python 提供了生成器表达式(generator expression),它是对列表
推导和生成器的一种泛化(
generalization)。生成器表达式在运行的时候,并不会把整个
输出序列都呈现出来,而是会估值为迭代器(
iterator),这个迭代器每次可以根据生成器
表达式产生一项数据。
把实现列表推导所用的那种写法放在一对圆括号中,就构成了生成器表达式。下
面给出的生成器表达式与刚才的代码等效。二者的区别在于,对生成器表达式求值的时

候,它会立刻返回一个迭代器,而不会深入处理文件中的内容。 

以刚才返回的那个迭代器为参数,逐次调用内置的 next 函数,即可使其按照生成
器表达式来输出下一个值。可以根据自己的需要,多次命令迭代器根据生成器表达式来
生成新值,而不用担心内存用量激增。

使用生成器表达式还有个好处,就是可以互相组合。下面这行代码会把刚才那个生
成器表达式所返回的迭代器用作另外一个生成器表达式的输入值。



第 10 条:尽量用 enumerate 取代 range 20
第 11 条:用 zip 函数同时遍历两个迭代器 21
第 12 条:不要在 for 和 while 循环后面写 else 块 23
第 13 条:合理利用 try/except/else/f inally 结构中的每个代码块 25
第 2 章 函数 28
第 14 条:尽量用异常来表示特殊情况,而不要返回 None 28
第 15 条:了解如何在闭包里使用外围作用域中的变量 30
第 16 条:考虑用生成器来改写直接返回列表的函数 35
第 17 条:在参数上面迭代时,要多加小心 37
第 18 条:用数量可变的位置参数减少视觉杂讯 41
第 19 条:用关键字参数来表达可选的行为 43
第 20 条:用 None 和文档字符串来描述具有动态默认值的参数 46
第 21 条:用只能以关键字形式指定的参数来确保代码明晰 49
第 3 章 类与继承 53
第 22 条:尽量用辅助类来维护程序的状态,而不要用字典和元组 53
第 23 条:简单的接口应该接受函数,而不是类的实例 58
第 24 条:以 @classmethod 形式的多态去通用地构建对象 62
第 25 条:用 super 初始化父类 67
第 26 条:只在使用 Mix-in 组件制作工具类时进行多重继承 71
第 27 条:多用 public 属性,少用 private 属性 75
第 28 条:继承 collections.abc 以实现自定义的容器类型 79
第 4 章 元类及属性 84
第 29 条:用纯属性取代 get 和 set 方法 84
第 30 条:考虑用 @property 来代替属性重构 88
第 31 条:用描述符来改写需要复用的 @property 方法 92
第 32 条:用 __getattr__、 __getattribute__ 和 __setattr__ 实现按需生成的属性 97
第 33 条:用元类来验证子类 102
第 34 条:用元类来注册子类 104
第 35 条:用元类来注解类的属性 108
第 5 章 并发及并行 112
第 36 条:用 subprocess 模块来管理子进程 113
第 37 条:可以用线程来执行阻塞式 I/O,但不要用它做平行计算 117
第 38 条:在线程中使用 Lock 来防止数据竞争 121XIV
第 39 条:用 Queue 来协调各线程之间的工作 124
第 40 条:考虑用协程来并发地运行多个函数 131
第 41 条:考虑用 concurrent.futures 来实现真正的平行计算 141
第 6 章 内置模块 145
第 42 条:用 functools.wraps 定义函数修饰器 145
第 43 条:考虑以 contextlib 和 with 语句来改写可复用的 try/f inally 代码 148
第 44 条:用 copyreg 实现可靠的 pickle 操作 151
第 45 条:应该用 datetime 模块来处理本地时间,而不是用 time 模块 157
第 46 条:使用内置算法与数据结构 161
第 47 条:在重视精确度的场合,应该使用 decimal 166
第 48 条:学会安装由 Python 开发者社区所构建的模块 168
第 7 章 协作开发 170
第 49 条:为每个函数、类和模块编写文档字符串 170
第 50 条:用包来安排模块,并提供稳固的 API 174
第 51 条:为自编的模块定义根异常,以便将调用者与 API 相隔离 179
第 52 条:用适当的方式打破循环依赖关系 182
第 53 条:用虚拟环境隔离项目,并重建其依赖关系 187
第 8 章 部署 193
第 54 条:考虑用模块级别的代码来配置不同的部署环境 193
第 55 条:通过 repr 字符串来输出调试信息 195
第 56 条:用 unittest 来测试全部代码 198
第 57 条:考虑用 pdb 实现交互调试 201
第 58 条:先分析性能,然后再优化 203
第 59 条:用 tracemalloc 来掌握内存的使用及泄漏情况 208


你可能感兴趣的:(【Python】)