我们来比较一下三种写法,我来阐述一下为什么 A最好,C最烂。
A. 初始化成[]然后“笨拙地”逐个append/extend
B. list(chain(*d.iteritems()))
C. list(sum(d.items(),()))
首先,跟 B 和 C 这样的 one liner 相比,A 这种看起来最“笨拙”、
似乎不怎么 "idiomatic" 的写法,实际上理解起来最容易,
几乎没有什么沟通成本和心智负担。
当然 itertools.chain 也是比较直观的,心智负担也不大。
而 sum(d.items(),()) 的问题,首先在于你要意识到 sum 函数第二个参数的存在,
而且这个参数的默认值是整数0,而不是 None, '', (), [], {} 或 set()。
这给大多数阅读代码的人带来了心智负担,降低了理解速度。
其次,我们来看看执行效率。
下面 benchmark 结果显示,在原 dict 稍大时,
裸写 []-extend 最快,chain 稍次之,sum 则完全不能忍受
——因为累加过程中无谓地生成了很多临时tuples,
跟众所周知的 字符串累加慢如蜗牛 一个道理。
In [22]: d = {i:str(i) for i in range(10000)}
...:
...: from itertools import chain
...: from timeit import timeit
...:
In [23]: def dtol1(d):
...: l = []
...: for k,v in d.iteritems():
...: l.append(k)
...: l.append(v)
...: return l
...: timeit('dtol1(d)', number=1000, setup='from __main__ import d,
dtol1')
...:
Out[23]: 3.0787
In [24]: def dtol2(d):
...: l = []
...: for kv in d.iteritems():
...: l.extend(kv)
...: return l
...: timeit('dtol2(d)', number=1000, setup='from __main__ import d,
dtol2')
...:
Out[24]: 1.8849 (Best)
In [25]: def dtol3(d):
...: return list(chain(*((k,v) for k,v in d.iteritems())))
...: timeit('dtol3(d)', number=1000, setup='from __main__ import d,
dtol3')
...:
Out[25]: 3.2725
In [26]: def dtol4(d):
...: return list(chain(*d.iteritems()))
...: timeit('dtol4(d)', number=1000, setup='from __main__ import d,
dtol4')
...:
Out[26]: 2.2957
In [27]: def dtol5(d):
...: return list(sum(d.iteritems(),()))
...: timeit('dtol5(d)', number=1000, setup='from __main__ import d,
dtol5')
...:
Out[27]: 479.147 (OMFG!)
In [28]: def dtol5a(d):
...: return list(sum(d.items(),()))
...: timeit('dtol5a(d)', number=1000, setup='from __main__ import d,
dtol5a')
...:
Out[28]: 477.200 (OMFG!)
再次,如果只需要得到 iterator,也是这种“笨拙”写法最容易修改成 generator 版
本,
利用 lazy evaluation 得到最高的效率。
In [29]: def dtol4_it(d):
...: return chain(*d.iteritems())
...: timeit('dtol4_it(d)', number=1000, setup='from __main__ import d,
dtol4_it')
...:
Out[29]: 0.9271
In [30]: def dtol5_it(d):
...: return iter(sum(d.iteritems(),()))
...: timeit('dtol5_it(d)', number=1000, setup='from __main__ import d,
dtol5_it')
...:
Out[30]: 479.2705 (OMFG!)
In [31]: def dtol2_it(d):
...: for k, v in d.iteritems():
...: yield k
...: yield v
...: timeit('dtol2_it(d)', number=1000, setup='from __main__ import d,
dtol2_it')
...:
Out[31]: 0.002788 (Best.)
(当然这里 dtol2_it 这个 generator 所返回的 iterator 的 .next() 方法
还没有开始执行,也就是说字典 d 还没有开始被遍历。)
综上,跟 one liner 相比,[]-extend/append 这种看似笨拙的写法
心智负担最小、执行效率最高、扩展/修改起来最灵活,正所谓大巧不工,
在我看来,至少在这个场景下,是所谓最 "pythonic" 的写法。
更进一步(不是特指这个例子,只是借题发挥一下,有 over-generalization 之嫌)
,
我隐约觉得在不少 Python 程序员中弥漫着一种 一味追求代码短小 的风气,
认为 代码写得短就是"Pythonic"。对此我保留意见。