python 拾遗
整理 python 使用的一些技巧,以及一些可能被忽略的细节,很多在文档可以查找到的内容将不会过多的描述,更多以外链的形式存在。
注意: 以下讨论主要为 Python2.7 版本, Python 3 的内容有待跟进
import
python 的 import 通过调用 _import_(name[, globals[, locals[, fromlist[, level]]]])
这个函数实现,借助这个函数可以通过 python 模块的名字动态引用模块。来自tornado 的 import_object 是一个很简洁的封装。
1 | def import_object(name): |
除此之外,我们还可以利用 python2.7 开始提供的 importlib.import_module (实现代码)来进行动态引用。例如:importlib.import_module('tornado.httpclient')
reload
当 reload 一个 python 模块之后有两处不会使用新的模块的值:
原来已经使用的实例还是会使用旧的模块,而新生产的实例会使用新的模块;
其他模块引用该模块的对象,这些引用不会绑定到新的对象上。
python-reloader 是一个很有趣的项目,它将 __builtin__.__import__
修改为自定义的 _import
函数,新的 _import
在原有调用 _import_(name[, globals[, locals[, fromlist[, level]]]])
的同时记录下引用模块之间的依赖关系。python-reloader 实现的是 reload 一个模块之后,reload 该模块所依赖的所有模块,而不是 reload 所有依赖该模块的模块。说起来很绕,这里 issue(It should reload dependants instead of dependencies)有很好的讨论。在实际运行的系统中动态 reload 模块可能并非一种很好的选择。
Catching an exception while using ‘with’
当我们使用 with 表达式并且需要捕捉异常的时候,我们可以这样做:
1 | try: |
如果希望捕捉 with 表达式的异常与内部工作代码的异常分离出来,我们可以这样做:
1 | try: |
关于 python 使用 try-except-else, with 的一些讨论:Is it a good practice to use try-except-else,Using python “with” statement with try-except block
StringIO
当我们使用一些接收参数是文件类型的 API 时,我们可能需要使用到 StringIO,例如使用 gzip 模块压缩一个字符串:
1 | import gzip, StringIO |
在 Python2.7 中 cStringIO 提供了与 StringIO 类似的接口,并且运行效率更高。在 Python3.4 中这两者被统一成为了 io.StringIO。
注意:cStringIO 的使用有一些限制:cStringIO 不能作为基类被继承;cStringIO 不能接收非 ASCII 字符的字符串参数;还有一点与 StringIO 不同的是当使用字符串参数初始化一个 cStringIO 对象时,该对象是只读的。
Queue.Queue vs collections.deque
Queue (python3 重命名为 queue)是一个可用于多线程之间同步、交换数据的队列模块,包括 FIFO,LIFO,优先级队列三个实现。
collections.deque 是一个双端队列的数据结构,在头和尾的插入、删除、读取操作是O(1)复杂度;在队列中部的随机读取操作是O(n)的。
reference:Python: Queue.Queue vs. collections.deque
heapq
python 内置的 heapq 是一个小顶堆,并且 heapify
, heappush
, heappop
操作是不支持传递 key
参数的。如果想实现大顶堆,可以这样 lambda x: -x
,或者自己封装一层。例如:
python topN max heap。另外,heapq.nlargest
和 heapq.nsmallest
支持 key 参数。
邮件列表里的讨论:为什么 python 的 heapq 没有支持 key 参数
itertools.tee
itertools.tee
从一个迭代器返回 n 个独立的迭代器,原始迭代器将不允许被使用,如果使用,那么可能会导致新的迭代器失效。Inside Python’s itertools.tee 很详细的探究了 itertools.tee
的实现细节。
when should we use operator
最常用的就是 operator.itemgetter
,例如我们有一个 tuple 列表,需要对这些元组按照第i个元素排序,那么可以这样:lst.sort(key=operator.itemgetter(i))
。
operator.add
与 lambda x, y: x+y
具有相同的效果,二者的不同主要有两个方面:一方面是它们的可读性、开发者的使用习惯的差别;另一方面是性能差别。在python wiki 中有这样一段话:
Likewise, the builtin functions run faster than hand-built equivalents. For example, map(operator.add, v1, v2) is faster than map(lambda x,y: x+y, v1, v2).
我们对二者做一次简单的实验对比:
1 | ➜ python -m timeit 'import operator' 'map(operator.add, [x for x in range(5000)], [y for y in range(5000)])' |
实验结果中,在 Python2.7 环境下 operator.add
稍微快于使用 lambda 表达式,在 Python3 环境下两者几乎没有差别。事实上 Python 并不适合 CPU 密集型的应用场景,当 CPU 不是性能瓶颈时,operator 和 lambda 之间的性能差距基本可以忽略。