如何循环遍历生成器

104

如何循环遍历生成器?我考虑了以下方法:

gen = function_that_returns_a_generator(param1, param2)
if gen: # in case the generator is null
    while True:
        try:
            print gen.next()
        except StopIteration:
            break

有更Pythonic的方式吗?


我建议使用 break 而不是 continue - Jon Clements
如果生成器可能在某个元素上抛出异常,但您不想停止迭代,我会建议以这种方式实现。 - robbrit
我想要测量每个生成器调用的执行时间。有什么比较优雅和符合Python风格的方式来构建一个循环,可以在每次调用之前和之后获取时间戳? - chrisinmtown
使用variant实际上是我使用情况下最好的选择,但在Python 3中必须使用gen.__next__() - Jann Poppinga
8个回答

179

简单地说

for x in gen:
    # whatever

这将起到作用。请注意,if gen 的返回值总是 True


6
不,if gen并不总是返回True。如果操作中的function_that_returns_a_generator()返回None,那么在if语句中,gen会被解析为False - drevicko
54
我假设 function_that_returns_a_generator() 返回一个生成器(这是一个大胆的假设,不是吗?)。None 不是一个生成器。 - Sven Marnach
由于OP要求“Pythonic方式”,鉴于Python倡导EAFP,这个答案似乎相当合理;-) - DerMike

19

如果你只关心生成器的副作用而不需要它的输出,你可以使用以下一行代码:

for _ in gen: pass

跟进

在aiven的评论后,我进行了一些性能测试,虽然list(gen)for _ in gen: pass稍微快一点,但是结果表明tuple(gen)更快。然而,正如Erik Aronesty正确指出的那样,tuple(gen)list(gen)都会存储结果,因此我的最终建议是使用

tuple(gen)

但前提是生成器不会循环数十亿次,以免占用过多内存。


7
或者只需使用list(gen)来生成列表。 - aiven
2
元组或列表将存储所有结果。如果生成器要循环数十亿次(有些会无限循环),那就不太好了。 - Erik Aronesty

17
for item in function_that_returns_a_generator(param1, param2):
    print item

如果函数没有返回任何值,你不需要担心是否有任何返回值,因为如果没有返回值,你将不会进入循环。


8
你可以简单地遍历它:
>>> gen = (i for i in range(1, 4))
>>> for i in gen: print i
1
2
3

但请注意,您只能循环一次。下一次生成器将为空:

>>> for i in gen: print i
>>> 

6
其他答案适用于复杂的场景。如果你只想将项目流式传输到列表中:
x = list(generator)

对于简单的预处理,使用列表推导式:

x = [tup[0] for tup in generator]

如果您只想执行生成器而不保存结果,可以跳过变量赋值:

# no var assignment b/c we don't need what print() returns
[print(_) for _ in gen]

如果您的生成器是无限的(比如从互联网流式传输项目),请不要这样做。列表构建是一个阻塞操作,直到生成器为空为止。

我没有注意到生成器只能使用一次,因此list(generator)正是我所需要的。 - Mike

4

把它当作任何其他可迭代对象一样处理:

for val in function_that_returns_a_generator(p1, p2):
    print val

请注意,if gen:将始终为真,因此这是一个错误的测试。

2
如果您想手动通过生成器移动(即手动处理每个循环),则可以执行以下操作:
    from pdb import set_trace

    for x in gen:
        set_trace()
        #do whatever you want with x at the command prompt
        #use pdb commands to step through each loop of the generator e.g., >>c #continue   

1
从pdb导入set_trace #没有() :) - Vlad K.

0
如果你有一个非常复杂的情况,比如一个命令数组,并且想要避免处理一个包含[None]*1000的列表,你可以这样做:
>>> d = {} # just a test-case scenario
>>> iterate = lambda *args: None # still invokes GC on (None)*1000
>>> iterate( *(d.__setitem__(k,None) for k in 'ABCDE') )
>>> d
{'A': None, 'D': None, 'C': None, 'B': None, 'E': None}

只是为了提供背景,展示被抓取的对象:

>>> d = {}
>>> iterate = lambda *args: print(args)
>>> iterate( *(d.__setitem__(k,None) for k in 'ABCDE') )
(None, None, None, None, None)
>>> d
{'A': None, 'D': None, 'C': None, 'B': None, 'E': None}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接