1.1 确认自己所用的Python版本
1 目前有两个版本的Python处于活跃状态:Python2和Python3.
2 流行的Python运行时(runtime)环境:CPython,Jython,IronPython以及PyPy等。
3 在操作系統的命令行中运行Python时,确保该Python的版本与你使用的Python版本相符。
4 由于Python社区把开发重点放在Python3上,所以在开发后续项目时,应该优先考虑采用Python3。
1.2 遵循PEP8风格指南
5 在编写Python代码时,应该遵循PEP8风格指南。
6 与广大Python开发者采用同一套代码风格,可以使项目更利于多人协作。
7 采用一致的风格来编写代码,可以令后续的修改工作变得更为容易。
1.3 了解bytes、str与unicode的区别
8 在Python3中,bytes是一种包含8位值得序列,str是一种包含Unicode字符的序列。开发者不能以>或+等操作来混同操作bytes和str实例。
9 在Python2中,str是一种包含8位值得序列,unicode是一种包含Unicode字符的序列。如果str只含有7位ASCII字符,那么可以通过相关的操作符来同时使用str与unicode。
10 在对输入的数据进行操作之前,使用辅助函数来保证字符序列的类型与开发者的期望相符。
11 从文件读取二进制数据或向其中写入二进制数据时,总应该以“rb”或“wb”等二进制模式来开启文件。
1.4 用辅助函数来取代复杂的表达式
12 开发者很容易过度运用Python的语法特性,从而写出那种特别复杂并且难以理解的单行表达式。
13 请把复杂的表达式移入到辅助函数之中,如果要反复使用相同的逻辑,就更应该封装到辅助函数中。
14 使用if/else表达式,要比使用or或and这样的Boolean操作符写成的表达式更加清晰。
1.5了解切割序列的办法
15 不要写多余的代码:当start索引为0,或end索引为序列长度时,应该将其省略。
16 切片操作不会计较start与end索引是否越界,这使得很容易就能从序列的前段或后端开始,对其进行范围固定的切片操作。
17 对list赋值的时候,如果使用切片操作,就会把原列表中处在相关范围的值替换成新值,即便长度不同也依然可以替换。
1.6 在单次切片操作内,不要同时指定start、end和stride
18 既有start和end,又有stride的切割操作,可能会令人费解。
19 尽量使用stride为正数,且不带start或end索引的切割操作。尽量避免使用负数做stride。
20 在同一个切片操作内,不要同时使用start、end和stride。如果确实需要执行这种操作,那就考虑将其拆解为两条赋值语句,其中一条做范围切割,另一条做步进切割。或考虑使用内置itertools模块中的islice。
1.7 用列表推导来取代map和filter
21 列表推导要比内置的map和filter函数清晰,因为无需额外编写lambda表达式。
22 列表推导可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter方能实现。
23 字典与集也支持推导表达式。
1.8 不要使用含有两个以上表达式的列表推导式
24 列表推导式支持多级循环,每一级循环也支持多项条件。
25 超过两个表达式的列表推导式很难理解的,尽量避免。
1.9 用生成器表达式来改写数据量较大的列表推导式
26 当输入的数据量较大时,列表推导可能会因为占用太多内存空间而出问题。
27 由生成器表达式所返回的迭代器,可以逐次产生输出值,从而避免内存用量的问题。
28 把某个生成器表达式所返回的迭代器,放在另一个生成器表达式的for子表达式中,即可将二者组合起来。
29 串在一起的生成器表达式执行速度很快。
1.10 尽量用enumerate取代range
30 enumerate 函数提供了一种精简的写法,可以在遍历迭代器时获知每个元素的索引。
31 尽量用enumerate来改写那种将range与下标访问相结合的序列遍历代码。
32 可以给enumerate提供第二个参数,以指定开始计数时所用的值(默认值为0)。
1.11 用zip函数同时遍历两个迭代器
33 内置zip函数可以平行地遍历多个迭代器。
34 Python3中的zip相当于生成器,会在遍历过程中逐次产生元组,而Python2中的zip则是直接把这些元组完全生成好,并一次性的返回整份列表。
35 如果提供的迭代器长度不等,那么zip就会自动提前终止。
36 itertools内置模块中的zip_longest函数可以平行地遍历多个迭代器,而不用在乎它们的长度是否相等。
1.12 不要在for和while循环的后面写else块
37 Python中有种特殊语法,可在for及while循环的内部语句块之后紧跟一个else块。
38 只有当整个循环主题都没有遇到break语句时,循环后面的else块才会执行。
39 不要在循环后面使用else块,因为这种写法既不直观,又容易引人误解。
1.13 合理利用try/except/else/finally结构中的每个代码块
40 无论try块是否发生异常,都可以利用try/finally复合语句中的finally块来执行清理工作。
41 else块可以用来缩减try块中的代码量,并把没有发生异常时所需要执行的语句与try/except代码块隔开。
42 顺利运行try块后,若想使某些操作能在finally块的清理代码之前执行,则可以将这些操作写到else块中。
2.14 尽量用异常来表示特殊情况,而不要返回None
1 函数在遇到特殊情况时,应该抛出异常,而不要返回None。调用者看到该函数的文档所描述的异常之后,应该就会编写相应的代码来处理它们了。
2 用None这个返回值来表示特殊意义的函数,很容易使调用者犯错,因为None和0及空字符串之类的值,在条件表达式里都会评估为False。
2.15 了解如何在闭包里使用外围作用域中的变量
2.16 考虑用生成器来改写直接返回列表的函数
8 使用生成器比把收集到的结果放入列表里返回给调用者更加清晰。
9 有生成器函数所返回的那个迭代器,可以把生成器函数体中,传给yield表达式的那些值逐次产生出来。
10 无论输入量有多大,生成器都能产生一系列输出,因为这些输入量和输出量都不会影响它的执行时所消耗的内存。
2.17 在参数上面迭代时,要多加小心
11 函数在输入的参数上面多次迭代时要当心:如果参数是迭代器,那么可能会导致奇怪的行为并错失某些值。
12 Python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式相互配合。
13 把__iter__方法实现为生成器,即可以定义自己的容器类型。
14 想判断某个值是迭代器还是容器,可以拿该值为参数,两次调用iter函数,若结果相同,则是迭代器,调用内置的next函数,即可令该迭代器前进一步。
2.18 用数量可变的位置参数减少视觉杂讯
15 在def语句中使用*args,即可令函数接受数量可变的位置参数。
16 调用函数时,可以采用*操作符,把序列中的元素当成位置参数,传给该函数。
17 对生成器使用*操作符,可能导致程序耗尽内存并崩溃。
18 在已经接受*args参数的函数上面继续添加位置参数,可能会产生难易排查的bug。
2.19 用关键字参数来表达可选的行为
19 函数参数可以按位置或关键字来制定。
20 只使用位置参数来调用函数,可能会导致这些参数值的含义不够明确,而关键字参数则能够阐明每个参数的意图。
21 给函数添加新的行为时,可以使用默认值的关键字参数,以便与原有的函数调用代码保持兼容。
22 可选的关键字参数,总应该以关键字形式来指定,而不应该以位置参数的形式来指定。
2.20 用None和文档字符串来描述具有动态默认值的参数
23 参数的默认值,只会在程序加载模块并读到本函数的定义时评估一次。对于{}或[]等动态的值,这可能给会导致奇怪的行为。
24 对于以动态值作为实际默认值的关键字参数来说,应该把形式上的默认值写为None,并在函数的文档字符串里面描述该默认值所对应的实际行为。
2.21 用只能以关键字形式指定的参数来确保代码明晰
25 关键字参数能够使函数调用的意图更加明确。
26 对于各参数之间很容易混淆的函数,可以声明只能以关键字形式指定的参数,以确保调用者必须通过关键字来指定它们。对于接受多个Booolean标志的函数,更应该这样做。
27 在编写函数时,Python3有明确的语法来定义这种只能以关键字形式指定的参数。
28 Python2的函数可以接受**kwargs参数,并手工抛出TypeError异常,以便模拟只能以关键字形式来指定的参数。
3.22 尽量用辅助类来维护程序的状态,而不要用字典和元组
1 不要使用包含其它字典的字典,也不要使用过长的元组。
2 如果容器中包含简单而又不可变的数据,那么可以先使用namedtuple来表示,待稍后有需要时,再修改为完整的类。
3 保存内部状态的字典如果变得比较复杂,那就应该把这些代码拆解为多个辅助类。
未完待续