如何在Python中制作重复生成器

时间:2009-09-03 23:12:33

标签: python

如何在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__返回一个新的迭代器)

6 个答案:

答案 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行和一些缩进,你可以在没有自定义装饰器的情况下完成。这包含两个技巧:

  1. [一般有用:]您可以轻松地使类可迭代而无需实现 .next() - 只需使用__iter__(self)生成器!

  2. 您可以在函数内定义一次性类,而不是使用构造函数。

  3. =&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]
>>>

简单。