执行生成器表达式的最优雅方式是什么?
越来越多的Python特性变得“懒惰执行”,比如生成器表达式和其他类型的迭代器。
不过,有时候我还是想写一个简单的“一行”循环,来执行某些操作。
那么,怎样做才能让这个循环真正执行呢?
举个例子:
a = open("numbers.txt", "w")
(a.write ("%d " % i) for i in xrange(100))
a.close()
这不是实际的代码,但你明白我的意思。如果我使用列表生成器,结果就是会创建一个长度为N的列表,里面全是“None”。
目前我做的是把这个表达式作为“any”或“all”函数的参数来使用。但我想找到一种方法,不依赖于循环中执行的表达式的结果——因为“any”和“all”会根据评估的表达式停止。
为了更清楚,这里是我已经知道的一些方法,每种方法都有它的缺点:
[a.write ("%d " % i) for i in xrange(100))]
any((a.write ("%d " % i) for i in xrange(100)))
for item in (a.write ("%d " % i) for i in xrange(100)): pass
4 个回答
如果我想做这个具体的例子,我会这样写
for i in xrange(100): a.write('%d ' % i)
如果我经常需要使用一个迭代器来达到某种效果,我会这样定义
def for_effect(iterable):
for _ in iterable:
pass
有一种显而易见的方法可以做到这一点,而且这就是你应该采用的方法。没有理由去用什么聪明的方式。
a = open("numbers.txt", "w")
for i in xrange(100):
a.write("%d " % i)
d.close()
懒惰执行给你带来了一个很大的好处:它允许你把一个序列传递给另一段代码,而不需要把整个序列都放在内存里。这是为了创建高效的序列作为数据类型。
在这种情况下,你并不需要懒惰执行。你需要的是执行。你可以直接... 执行。使用一个 for
循环就可以了。
现在是2019年——这是一个2010年的问题,至今仍然被提起。在Python的一个邮件列表中,最近有个讨论关于这个话题,发了超过70封邮件,但他们又拒绝在语言中添加一个叫consume
的功能。
在那个讨论中,出现了一种效率最高的方法,虽然这并不明显,所以我在这里把它作为答案发出来:
import deque
consume = deque(maxlen=0).extend
然后使用consume
这个可调用对象来处理生成器表达式。
结果发现,cPython中的deque
原生代码其实是针对maxlen=0
的情况进行了优化,它会直接消耗这个可迭代对象。
我在问题中提到的any
和all
的调用应该也同样高效,但要确保表达式的真假性,以便可迭代对象能够被消耗。
我知道这可能仍然有争议,毕竟用一个明确的两行for循环也能处理这个问题——我记得这个问题是因为我刚刚提交了一个代码,其中我创建了一些线程,启动它们,然后再把它们合并回来——没有使用consume
这个可调用对象,这样就需要4行代码,大部分是模板代码,而且没有利用原生代码循环遍历可迭代对象的好处:https://github.com/jsbueno/extracontext/blob/a5d24be882f9aa18eb19effe3c2cf20c42135ed8/tests/test_thread.py#L27