我有一个由多重yield返回的生成器对象。准备调用这个生成器是相当耗时的操作。这就是为什么我想重复使用发电机几次。
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
当然,我正在考虑将内容复制到简单的列表中。有办法重置我的发电机吗?
请参见:如何提前查看Python生成器中的一个元素?
我有一个由多重yield返回的生成器对象。准备调用这个生成器是相当耗时的操作。这就是为什么我想重复使用发电机几次。
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
当然,我正在考虑将内容复制到简单的列表中。有办法重置我的发电机吗?
请参见:如何提前查看Python生成器中的一个元素?
当前回答
可能最简单的解决方案是将昂贵的部分包装在一个对象中,并将其传递给生成器:
data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass
这样,就可以缓存昂贵的计算。
如果您可以同时将所有结果保存在RAM中,那么可以使用list()将生成器的结果物化到一个普通列表中并使用该列表。
其他回答
你可以使用itertools.cycle()来实现这一点。 您可以使用此方法创建一个迭代器,然后在迭代器上执行for循环,迭代器将对其值进行循环。
例如:
def generator():
for j in cycle([i for i in range(5)]):
yield j
gen = generator()
for i in range(20):
print(next(gen))
将生成20个数字,0到4重复。
医生说:
Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
可能最简单的解决方案是将昂贵的部分包装在一个对象中,并将其传递给生成器:
data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass
这样,就可以缓存昂贵的计算。
如果您可以同时将所有结果保存在RAM中,那么可以使用list()将生成器的结果物化到一个普通列表中并使用该列表。
使用包装器函数来处理StopIteration
您可以为生成器生成函数编写一个简单的包装器函数,用于跟踪生成器耗尽的时间。它将使用生成器在迭代结束时抛出的StopIteration异常来完成此操作。
import types
def generator_wrapper(function=None, **kwargs):
assert function is not None, "Please supply a function"
def inner_func(function=function, **kwargs):
generator = function(**kwargs)
assert isinstance(generator, types.GeneratorType), "Invalid function"
try:
yield next(generator)
except StopIteration:
generator = function(**kwargs)
yield next(generator)
return inner_func
如上所述,当包装器函数捕获到StopIteration异常时,它只是重新初始化生成器对象(使用函数调用的另一个实例)。
然后,假设你定义了如下所示的生成器提供函数,你可以使用Python函数装饰器语法来隐式包装它:
@generator_wrapper
def generator_generating_function(**kwargs):
for item in ["a value", "another value"]
yield item
我不知道你说的昂贵的准备是什么意思,但我猜你确实有
data = ... # Expensive computation
y = FunctionWithYield(data)
for x in y: print(x)
#here must be something to reset 'y'
# this is expensive - data = ... # Expensive computation
# y = FunctionWithYield(data)
for x in y: print(x)
如果是这样的话,为什么不重用数据呢?
如果你的生成器在某种意义上是纯的,它的输出只依赖于传递的参数和步长,并且你希望生成的生成器是可重新启动的,这里有一个排序代码片段可能很方便:
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