如何在Python中创建像xrange这样的重复生成器?例如,如果我这样做:
>>> m = xrange(5)
>>> print list(m)
>>> print list(m)
我两次得到相同的结果 - 数字0..4。但是,如果我尝试使用yield:
>>> def myxrange(n):
... i = 0
... while i < n:
... yield i
... i += 1
>>> m = myxrange(5)
>>> print list(m)
>>> print list(m)
我第二次尝试迭代m时,我什么也得不回来 - 一个空列表。
是否有一种简单的方法可以创建像xrange那样的带有yield或生成器理解的重复生成器?我发现a workaround on a Python tracker issue,它使用装饰器将生成器转换为迭代器。每次开始使用它时都会重新启动,即使您上次没有使用所有值,就像xrange一样。我也提出了我自己的装饰器,基于相同的想法,实际上返回一个生成器,但一个可以在抛出StopIteration异常后重新启动:
@decorator.decorator
def eternal(genfunc, *args, **kwargs):
class _iterable:
iter = None
def __iter__(self): return self
def next(self, *nargs, **nkwargs):
self.iter = self.iter or genfunc(*args, **kwargs):
try:
return self.iter.next(*nargs, **nkwargs)
except StopIteration:
self.iter = None
raise
return _iterable()
有没有更好的方法来解决问题,只使用产量和/或生成器理解?或者内置于Python中的东西?所以我不需要滚动自己的类和装饰器?
comment by u0b34a0f6ae确定了我误解的根源:
xrange(5)不返回迭代器,它会创建一个xrange对象。 xrange对象可以像字典一样迭代,不止一次。
我的“永恒”函数完全咆哮错误的树,通过像迭代器/生成器(__iter__
返回self)而不是像集合/ xrange一样(__iter__
返回一个新的迭代器)
答案 0 :(得分:17)
不直接。允许生成器用于实现协同例程,资源管理等的部分灵活性是它们始终是一次性的。一旦运行,就无法重新运行发电机。您必须创建一个新的生成器对象。
但是,您可以创建自己的类来覆盖__iter__()
。它将像一个可重复使用的生成器:
def multigen(gen_func):
class _multigen(object):
def __init__(self, *args, **kwargs):
self.__args = args
self.__kwargs = kwargs
def __iter__(self):
return gen_func(*self.__args, **self.__kwargs)
return _multigen
@multigen
def myxrange(n):
i = 0
while i < n:
yield i
i += 1
m = myxrange(5)
print list(m)
print list(m)
答案 1 :(得分:2)
使用itertools非常容易。
import itertools
alist = [1,2,3]
repeatingGenerator = itertools.cycle(alist)
print(next(generatorInstance)) #=> yields 1
print(next(generatorInstance)) #=> yields 2
print(next(generatorInstance)) #=> yields 3
print(next(generatorInstance)) #=> yields 1 again!
答案 2 :(得分:1)
如果你写了很多这些,John Millikin的回答是最干净的。
但是如果你不介意添加3行和一些缩进,你可以在没有自定义装饰器的情况下完成。这包含两个技巧:
[一般有用:]您可以轻松地使类可迭代而无需实现
.next()
- 只需使用__iter__(self)
生成器!
您可以在函数内定义一次性类,而不是使用构造函数。
=&GT;
def myxrange(n):
class Iterable(object):
def __iter__(self):
i = 0
while i < n:
yield i
i += 1
return Iterable()
小字体:我没有测试性能,这样产生类可能会浪费。但很棒; - )
答案 3 :(得分:0)
我认为答案是“不”。我可能错了。对于2.6中涉及参数和异常处理的生成器,你可以使用一些时髦的新东西,这可能会产生你想要的东西。但这些功能主要用于实现半连续性。
为什么你不想拥有自己的类或装饰器?你为什么要创建一个返回生成器而不是类实例的装饰器?
答案 4 :(得分:0)
您可以使用第三方工具more_itertools.seekable
重置迭代器。
通过> pip install more_itertools
安装。
import more_itertools as mit
def myxrange(n):
"""Yield integers."""
i = 0
while i < n:
yield i
i += 1
m = mit.seekable(myxrange(5))
print(list(m))
m.seek(0) # reset iterator
print(list(m))
# [0, 1, 2, 3, 4]
# [0, 1, 2, 3, 4]
注意:在推进迭代器的过程中,内存消耗会增加,所以请小心翼翼地包装大型迭代器。
答案 5 :(得分:-1)
使用此解决方案:
>>> myxrange_ = lambda x: myxrange(x)
>>> print list(myxrange_(5))
... [0, 1, 2, 3, 4]
>>> print list(myxrange_(5))
... [0, 1, 2, 3, 4]
>>> for number in myxrange_(5):
... print number
...
0
1
2
3
4
>>>
和装饰者:
>>> def decorator(generator):
... return lambda x: generator(x)
...
>>> @decorator
>>> def myxrange(n):
... i = 0
... while i < n:
... yield i
... i += 1
...
>>> print list(myxrange(5))
... [0, 1, 2, 3, 4]
>>> print list(myxrange(5))
... [0, 1, 2, 3, 4]
>>>
简单。