最近在读《编写高质量代码 改善Python程序的91个建议》,在这里总结阅读中遇到的一些要点,和一些自己的理解
0. the Zen of Python
先来看看一个有趣的彩蛋,python的设计之禅,我们在Python控制台输入import this
,可以看到
1 | import this |
python的设计哲学可以归纳为两个单词:简单,易懂
1. 理解Pythonic
什么是Pythonic,最直观的解释就是Python风格的代码,那什么是Python风格的代码
看看下面这个C语言的例子
1 | for (i = 0; i < mylist_length; i++) { |
如果直接写成Python的风格,是这样的(Python的for
语句只用于迭代,故我们把上面的写法转成while
语句)
1 | i = 0 |
上面代码可以正确运行,但是并不被人为是Python的风格,我们稍作修改
1 | for i in range(mylist_length): |
上面代码比之前的while
更为简洁,但还不是完全的Pythonic风格,下面方式才是完全的Pythonic风格
1 | for element in mylist: |
从上面例子中我们可以看出,Pythonic的代码,变量更少,更为短小,更为简单,读起来更为清晰
另外一个经常被提到的问题是,如何直接修改引用的变量(指针变量),我们再来看另外一个C语言的例子
1 | void foo(int* a, float* b) { |
上面代码不能很好的描述其功能,并且晦涩难懂,在Python不鼓励这种写法,也不支持这种写法,Python使用输入输出的方式传值
1 | def foo(): |
再看一个例子,在C语言中交换两个数
1 | int a = 1, b = 2 |
Python中交换两个数
1 | a, b = b, a |
Pythonic是一种代码风格,以简单,易懂为宗旨
参考:http://blog.startifact.com/posts/older/what-is-pythonic.html
2. 编写Pythonic代码
变量名不与内建方法重名,如dict, list, element等,
由于Python使用缩进识别代码块,所以在代码里面,多余的空格和Tab尽量不要随便使用,不推荐对齐等号的方式(下面方式)
1
2a = 10 # some comment
some_str = 'hello world' # some comment注释
1
2
3# 下面第一种比较第二行更好
x = x + 1 # increase x by 1
x = x + 1 # increase x by 1函数详细注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def func(a, b):
"""summary desctiption
more detail comments for the function
Args:
a: some comment for parameter a
b: some comment for parameter b
Returns:
return type, return value desctiption
Raises:
IOError: IOError exception may raise in the function
IndexError: IndexError exception may raise in the function
"""函数设计
- 函数长度不宜过长,通常以小于一屏为准
- 函数嵌套不宜过多,通常保持在3层以内(for, if-else等)
- 参数不宜过多
- 函数只做一件事
- 使用异常抛出错误,而不通过返回值错误
- 尽量不要在函数中定义可变对象,除非特殊需要
3. 常量
Python没有提供常量的支持,通常使用命名规范识别常量,所有字母大写,如MAX_OVERFLOW
,当然,这只是一种约定,实际上与变量一样,是可以改变的
还有一种方式来模拟实现常量的功能,使用类来限制对属性的赋值
1 | class _const: |
4. 使用断言
断言在其他很多语言都存在,可以方便用于测试和调试程序,使用断言格式如下
1 | assert expression, some_error_info |
上面例子相当于
1 | x, y = 1, 2 |
断言会带来一定的性能消耗,由于Python没有严格意义上的Debug和Release模式,故它并不优化字节码,只是忽略相关代码的执行,在执行脚本的时候添加-O
参数可以禁用断言
5. 使用枚举
Python本身并不提供枚举的功能,关于Python是否要加入枚举功能,也引发了很多讨论,最后被组织拒绝了(在Python3.4以后又支持了😅),但是因为Python强大的动态性,我们可以通过很多方式实现枚举的功能
1 | class Seasons: |
我们还可以用第三方模块实现枚举的功能flufl.enum
//TODO:
6. 不推荐使用type()来判断类型相等
在经典类中,所有对象执行type()都相等
在新式类中,type()无法用于判断子类与父类的关系
1
type(son) is type(parent) # 正常逻辑应该为True,但是结果是False
通常使用isinstance()方法判断类型
1
2isinstance(son, Son) # True
isinstance(son, (Son, list, tuple)) # True
7. 注意运算时候的精度问题
Python与C语言一样,计算精度取决于计算的值的类型,如两个整数相除,结果是整数,如果需要获得高精度的结果,需要转换为float类型在进行计算,在python3里,这个问题不存在
1 | a, b = 1, 3 |
8. 尽量避免浮点类型的比较
浮点类型,在计算过程中可能有精度损失的风险,应尽量避免,如果可以转成整形再计算
1 | i = 1.0 |
上面语句会一直在while循环,而不能正确跳出
9. 避免使用eval
用过JS或PHP的可能都知道eval函数,可以直接执行字符串脚本,然而,字符串有注入的风险,有安全性问题,如果需要,可以考虑使用ast.literal_eval
代替
//TODO
10. 使用enumerate枚举索引和变量
Python语言很灵活,同一功能有多种实现方式,如索引列表
1 | l = [1, 2, 3] |
推荐使用方式四,支持延迟加载,不会一次枚举出所有的值,性能最优,书写也简洁,enumerate不适用于dict对象
11. 分清is和==
is:比较内存地址,而不是内容,a is b
相当于id(a) == id(b)
==:a == b
相当于a.__eq__(b)
,可以重载__eq__
方法实现等于的逻辑
1 | class person(object): |
12. 尽量使用Unicode编码
python编码见这里
13. 多使用模块和包来管理文件
- 尽量减少使用
from pack import *
这种导入方式,会污染命名空间,容易导致命名冲突,如果冲突,则后导入的覆盖先导入的 - TODO: absolute import, relative import
14. Python不支持++i语法
Python会把++i
解释为+(+i)
1 | a = -1 |
15. 使用with自动释放资源
用过C#的朋友应该都知道using,可以在代码块结束后自动释放资源,而Python也支持类似的语法
1 | import io |
像上面这种IO资源,在使用完成后需要开发者自己调用释放资源的方法,通常也会使用try-except-finall
y来保证释放资源,而通常情况下,释放资源很容易遗漏,可以使用with
语法,把相关操作放在代码块中,当离开代码块的时候会自动调用释放资源的方法,这样就可以避免人为的遗漏问题,上面代码可以写成下面方式
1 | with open('test.txt', 'r') as f: |
无论代码块中是否会抛出异常,离开代码块的时候,资源f都会被释放,其实只需要实现__enter__
和__exit__
方法就能支持这种行为
1 | class MyObj(object): |