Python进阶自检清单:来自《Effective Python》的建议(三)

本系列主要介绍《Effective Python》所推荐的最佳实践,经验丰富的程序员应该对这些实践都比较熟悉了。文中有些补充信息是我自己的理解,请大家多指教!具体分析还请参考原书内容。


五:并发与并行(6条建议)

1. 用subprocess模块来管理子进程;

这里涉及到 subprocessmultiprocessing 模块的异同问题,一般 subprocess 用于在 Python 代码中调用其它程序,特别是不是用 Python 代码写的程序,比如 cmd 命令,开启其它应用等;而 multiprocessing 的接口和 threading 很相似,常用于分解我们自己代码中的任务,加快计算速度。

2. 用多线程处理 I/O 密集型任务,但不要用来处理计算密集型任务;

3. 在多线程中使用 Lock 来防止数据竞争;

这里涉及到对 GIL 的理解问题。由于 GIL 的存在,Python 中的多线程没法并行执行。如果我们进行一些测试,也会发现多个线程对同一个对象进行修改,一般是不会出错的。

但是,这个不出错纯粹是概率问题,因为 GIL 进行线程切换的过程对我们是不透明的。Python3.2 之前使用解释器计步(ticks),经过一定计算量就切换线程;3.2 之后对线程时间进行统计,一个线程占用 5 毫秒之后,如果有其它线程请求 GIL 锁,则会切换线程。

一些我们看上去的原子操作,实际上是涉及多个步骤的,比如 += 等,如果在步骤之间发生了线程切换,就会导致错误。因此,多线程处理同一个对象还是要加互斥锁的。

4. 用 Queue 来协调各线程之间的工作;

5. 用协程来并发地运行多个函数;

一个正在执行的线程需要大约 8MB 内存,一个协程占用不到 1KB;而且线程启动慢,为了实现线程安全还要写不少辅助代码。

python3.5 之后,引入asyncawait 关键字,协程的使用明确了很多。

6. 考虑用 multiprocessing 实现多线程;

multiprocessing 开销比较大,因为主进程和子进程之间的通信,必须进行序列化和反序列化操作。如果进程之间要传输很多数据,而每个进程的计算量相对较小,必须通过一些高级特性,比如共享内存来避免开销过高带来的问题。但一般情况下,不建议使用这些高级特性。


六:内置模块(7条建议)

1. 用 functools.wraps 定义函数修饰器;

解决命名空间规则带来的被装饰函数的名称改变的问题。

2. 考虑用 contextlib 和 with 语句来改写可复用的 try/finally 语句;

3. 用 copyreg 实现可靠的 pickle 操作;

使用很麻烦,数据结构复杂时,建议还是直接抛弃 pickle,用 json 保存数据。

4. 用 datetime 模块来处理本地时间,而不是 time 模块;

time 模块依赖操作系统,没法处理不同时区之间的转换,datetime 处理时区实际上也依赖另一个模块,即 pytz 对时区的定义。开发者应该以 UTC 为中介进行时区转换。

5. 使用内置算法和数据结构;

deque、OrderedDict、defaultdict、heap 等常用数据结构,bisect 二分操作模块,以及 itertools 这个迭代器常用函数包。

6. 高精度计算应该使用 decimal;

7. 学会搜索和使用第三方模块;


七:协作开发(5条建议)

1. 为每个函数、类和模块编写文档;

2. 用包来安排模块,并提供稳固的 API;

通过 __all__ 属性和 __init__.py 文件提供公共 API ,避免内部结构的暴露,之后修改包的内容,也不会影响旧的调用代码。

3. 为自编的模块定义根异常,以便将调用者与 API 相隔离;

4. 学会处理循环依赖问题;

5. 使用虚拟环境;


八:部署(6条建议)

1. 考虑用单独的文件来配置不同的部署环境,一般即 config.py;

2. 通过 repr 字符串来输出调试信息;

3. 用 unittest 来测试全部代码;

4. 考虑用 pdb 实现交互调试;

5. 先分析性能,然后再优化;

6. 用 tracemalloc 来掌握内存的使用及泄露情况;


发表评论

评论列表,共 0 条评论