在Python中重置生成器对象

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

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

当然,我正在考虑将内容复制到简单列表中。

另一个select是使用itertools.tee()函数来创build你的生成器的第二个版本:

 y = FunctionWithYield() y, y_backup = tee(y) for x in y: print(x) for x in y_backup: print(x) 

如果原始迭代可能不处理所有项目,这可以从内存使用的angular度来看是有益的。

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

  1. 再次运行发生器function,重新启动代:

     y = FunctionWithYield() for x in y: print(x) y = FunctionWithYield() for x in y: print(x) 
  2. 将生成器结果存储在内存或磁盘上的数据结构中,您可以再次进行迭代:

     y = list(FunctionWithYield()) for x in y: print(x) # can iterate again: for x in y: print(x) 

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

所以你有经典的内存与处理权衡 。 我无法想象没有存储值或重新计算它们的方法。

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

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

这样,您可以caching昂贵的计算。

如果可以同时将所有结果保存在RAM中,则使用list()将生成器的结果具体化为普通列表,然后使用该列表。

 >>> def gen(): ... def init(): ... return 0 ... i = init() ... while True: ... val = (yield i) ... if val=='restart': ... i = init() ... else: ... i += 1 >>> g = gen() >>> g.next() 0 >>> g.next() 1 >>> g.next() 2 >>> g.next() 3 >>> g.send('restart') 0 >>> g.next() 1 >>> g.next() 2 

我想为老问题提供一个不同的解决scheme

 class ReusableGenerator: def __init__(self, generator_factory): self.generator_factory = generator_factory def __iter__(self): return self.generator_factory() squares = ReusableGenerator(lambda: (x * x for x in range(5))) for x in squares: print(x) for x in squares: print(x) 

这是你将如何使用它

 y = ReusableGenerator(function_with_yield) for x in y: print(x) for x in y: print(x) 

list(generator)相比,这样做的好处是这是O(1)空间复杂度和list(generator)O(n) 。 缺点是,如果只能访问生成器,而不能生成生成器的函数,则不能使用此方法。 例如, 执行以下操作似乎是合理的,但不起作用。

 g = (x * x for x in range(5)) squares = ReusableGenerator(lambda: g) for x in squares: print(x) for x in squares: print(x) 

如果GrzegorzOledzki的答案不够,你可以使用send()来实现你的目标。 有关增强型发电机和增益expression式的更多详细信息,请参阅PEP-0342 。

更新:另请参阅itertools.tee() 。 它涉及到上面提到的一些内存与处理权衡,但是它可能会节省一些内存,而不是将生成器结果存储在一个list 。 这取决于你如何使用发生器。

如果你定义你自己的生成器函数,并希望生成的生成器是可重启的,下面是一个可能很方便的sorting代码片段:

 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 

从tee的官方文档 :

通常,如果一个迭代器在另一个迭代器启动之前使用大部分或全部数据,则使用list()而不是tee()会更快。

所以最好使用list(iterable)来代替你的情况。

没有选项来重置迭代器。 迭代器通常在迭代next()函数时popup。 唯一的方法是在对迭代器对象进行迭代之前进行备份。 检查下面。

用项目0到9创build迭代器对象

 i=iter(range(10)) 

遍历next()函数将popup

 print(next(i)) 

将迭代器对象转换为列表

 L=list(i) print(L) output: [1, 2, 3, 4, 5, 6, 7, 8, 9] 

所以项目0已经popup。 当我们将迭代器转换为列表时,所有项目也会popup。

 next(L) Traceback (most recent call last): File "<pyshell#129>", line 1, in <module> next(L) StopIteration 

所以你需要在开始迭代之前将迭代器转换为备份列表。 列表可以转换为迭代器与iter(<list-object>)

我不确定你昂贵的准备是什么意思,但我猜你实际上有

 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) 

如果是这样的话,为什么不重用data呢?

你可以定义一个返回你的生成器的函数

 def f(): def FunctionWithYield(generator_args): code here... return FunctionWithYield 

现在,只要你喜欢,你可以做很多次:

 for x in f()(generator_args): print(x) for x in f()(generator_args): print(x) 

好的,你说你想多次调用一个生成器,但初始化是昂贵的…这样的事情呢?

 class InitializedFunctionWithYield(object): def __init__(self): # do expensive initialization self.start = 5 def __call__(self, *args, **kwargs): # do cheap iteration for i in xrange(5): yield self.start + i y = InitializedFunctionWithYield() for x in y(): print x for x in y(): print x 

或者,你可以创build自己的类,遵循迭代器协议,并定义某种“重置”function。

 class MyIterator(object): def __init__(self): self.reset() def reset(self): self.i = 5 def __iter__(self): return self def next(self): i = self.i if i > 0: self.i -= 1 return i else: raise StopIteration() my_iterator = MyIterator() for x in my_iterator: print x print 'resetting...' my_iterator.reset() for x in my_iterator: print x 

https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html

它可以通过代码对象完成。 这里是例子。

 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