我有一个由多重yield返回的生成器对象。准备调用这个生成器是相当耗时的操作。这就是为什么我想重复使用发电机几次。

y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)

当然,我正在考虑将内容复制到简单的列表中。有办法重置我的发电机吗?


请参见:如何提前查看Python生成器中的一个元素?


当前回答

现在可以使用more_itertools。Seekable(第三方工具),允许重置迭代器。

通过> pip Install more_itertools安装

import more_itertools as mit


y = mit.seekable(FunctionWithYield())
for x in y:
    print(x)

y.seek(0)                                              # reset iterator
for x in y:
    print(x)

注意:内存消耗会随着迭代器的增加而增加,所以要警惕大型迭代对象。

其他回答

发电机不能倒带。您有以下选项:

再次运行生成器函数,重新启动生成: y = FunctionWithYield() 对于y中的x: print(x) y = FunctionWithYield() 对于y中的x: print(x) 将生成器结果存储在内存或磁盘上的数据结构中,您可以再次迭代: y = list(FunctionWithYield()) 对于y中的x: print(x) #可以再次迭代: 对于y中的x: print(x)

选项1的缺点是它会再次计算值。如果这是cpu密集型的,那么最终需要计算两次。另一方面,2的缺点是存储空间。整个值列表将存储在内存中。如果有太多的价值,那可能是不切实际的。

所以你有一个经典的内存和处理的权衡。我无法想象在不存储值或不重新计算它们的情况下倒带生成器的方法。

您也可以像其他答案所建议的那样使用tee,但是在您的情况下,它仍然会将整个列表存储在内存中,因此它将得到与选项2相同的结果和类似的性能。

如果希望使用预定义的参数集多次重用此生成器,可以使用functools.partial。

from functools import partial
func_with_yield = partial(FunctionWithYield, arg0, arg1)

for i in range(100):
    for x in func_with_yield():
        print(x)

这将把生成器函数包装到另一个函数中,因此每次调用func_with_yield()时,它都会创建相同的生成器函数。

它可以通过code对象来实现。下面是一个例子。

code_str="y=(a for a in [1,2,3,4])"
code1=compile(code_str,'<string>','single')
exec(code1)
for i in y: print i

1 2 3 4

for i in y: print i


exec(code1)
for i in y: print i

1 2 3 4

可能最简单的解决方案是将昂贵的部分包装在一个对象中,并将其传递给生成器:

data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass

这样,就可以缓存昂贵的计算。

如果您可以同时将所有结果保存在RAM中,那么可以使用list()将生成器的结果物化到一个普通列表中并使用该列表。

如果你的生成器在某种意义上是纯的,它的输出只依赖于传递的参数和步长,并且你希望生成的生成器是可重新启动的,这里有一个排序代码片段可能很方便:

import copy

def generator(i):
    yield from range(i)

g = generator(10)
print(list(g))
print(list(g))

class GeneratorRestartHandler(object):
    def __init__(self, gen_func, argv, kwargv):
        self.gen_func = gen_func
        self.argv = copy.copy(argv)
        self.kwargv = copy.copy(kwargv)
        self.local_copy = iter(self)

    def __iter__(self):
        return self.gen_func(*self.argv, **self.kwargv)

    def __next__(self):
        return next(self.local_copy)

def restartable(g_func: callable) -> callable:
    def tmp(*argv, **kwargv):
        return GeneratorRestartHandler(g_func, argv, kwargv)

    return tmp

@restartable
def generator2(i):
    yield from range(i)

g = generator2(10)
print(next(g))
print(list(g))
print(list(g))
print(next(g))

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
0
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1