在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:
-
再次运行发生器function,重新启动代:
y = FunctionWithYield() for x in y: print(x) y = FunctionWithYield() for x in y: print(x)
-
将生成器结果存储在内存或磁盘上的数据结构中,您可以再次进行迭代:
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