【问题标题】:How to make a repeating generator in Python如何在 Python 中制作重复生成器
【发布时间】:2009-09-03 23:12:33
【问题描述】:

如何在 Python 中制作重复生成器,例如 xrange?例如,如果我这样做:

>>> m = xrange(5)
>>> print list(m)
>>> print list(m)

我两次得到相同的结果——数字 0..4。但是,如果我尝试使用相同的产量:

>>> def myxrange(n):
...   i = 0
...   while i < n:
...     yield i
...     i += 1
>>> m = myxrange(5)
>>> print list(m)
>>> print list(m)

第二次尝试迭代 m 时,我什么也没得到——一个空列表。

有没有一种简单的方法来创建像 xrange 这样的重复生成器,或者生成器理解?我找到了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()

有没有更好的方法来解决这个问题,只使用 yield 和/或生成器理解?还是 Python 内置的东西?所以我不需要推出自己的类和装饰器?

更新

comment by u0b34a0f6ae 指出了我误会的根源:

xrange(5) 不返回迭代器,它创建一个 xrange 对象。 xrange 对象可以多次迭代,就像字典一样。

我的“永恒”函数完全是错误的树,就像一个迭代器/生成器(__iter__ 返回自身)而不是一个集合/xrange(__iter__ 返回一个新迭代器)。

【问题讨论】:

  • 小挑剔,但xrange() 不是生成器。 type(xrange(4)) != type(myxrange(4)).
  • 我认为这不仅仅是一个小问题。这就是差异的全部原因。正如约翰指出的那样,可以通过重载的 iter 获得所需的行为。
  • 与其他两个提议的实现相比(一个在您链接到的 Python 跟踪器问题中,另一个在 @JohnMillikin 的答案中),我在遵循您的实现代码时遇到了很多麻烦。特别是,难以弄清楚:(1)“@decorator.decorator”到底是什么意思。你能给它一个文档的链接吗? (2)一个用法示例将非常有帮助;特别是一个练习 args 和 nargs (3) 的例子,你能举例说明你的 StopIteration 处理如何增加价值吗?即您的实现成功但其他两个实现失败的示例。
  • @DonHatch 希望我刚刚添加到我的问题的更新解释了为什么我的实现代码难以理解(并且完全错误)。

标签: python


【解决方案1】:

不直接。允许生成器用于实现协同程序、资源管理等的部分灵活性在于它们始终是一次性的。一旦运行,生成器就不能重新运行。您必须创建一个新的生成器对象。

但是,您可以创建自己的类来覆盖__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)

【讨论】:

  • 这与我在问题中链接到的解决方法基本相同——我猜你错过了。不过谢谢!
  • “解决方法”。它没有那么多。 xrange(5) 不返回迭代器,它创建一个 xrange 对象。 xrange 对象可以多次迭代,就像字典一样。
【解决方案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!

【讨论】:

  • 这会导致print list(m)(问题中的一个要求)永远运行,但是:/
【解决方案3】:

如果你写了很多这些,John Millikin 的答案是最干净的。

但如果你不介意添加 3 行和一些缩进,你可以在没有自定义装饰器的情况下完成。这构成了 2 个技巧:

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

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

=>

def myxrange(n):
    class Iterable(object):
        def __iter__(self):
            i = 0
            while i < n:
                yield i
                i += 1
    return Iterable()

小字:我没有测试性能,生成这样的类可能是浪费。但是很棒;-)

【讨论】:

    【解决方案4】:

    我认为答案是“不”。我可能错了。可能是在 2.6 中您可以使用生成器做一些时髦的新事情,包括参数和异常处理,这将允许您想要的东西。但这些功能主要用于实现半延续。

    为什么你不想拥有自己的类或装饰器?为什么要创建一个返回生成器而不是类实例的装饰器?

    【讨论】:

    • (1) 因为它需要更多的代码,而且似乎是以某种我不知道的方式实现的东西。 (2) 因为我最初误解了 xrange,并认为我想要一个永恒的生成器而不是一个可迭代的。
    【解决方案5】:

    您可以使用第三方工具 more_itertools.seekable 重置迭代器。

    通过&gt; 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]
    

    注意:在推进迭代器时内存消耗会增加,所以要小心包装大型迭代器。

    【讨论】:

      【解决方案6】:

      使用这个解决方案:

      >>> 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]
      >>>
      

      简单。

      【讨论】:

      • ¿-1?,不明白这个。该解决方案满足问题的要求。
      • 不,它没有,因为重复迭代所需的参数没有封装在对象中 - 每次需要新的重用时都必须传递它们。生成器对象的全部意义在于封装执行迭代所需的信息。可重复使用的生成器也应该这样做。请注意,John Millikin 的解决方案并不要求您每次使用生成器时都输入“5”。您输入“5”一次,对象会从那时起为您记住它。
      • 另外,语法与生成器不同。需要额外的括号。
      猜你喜欢
      • 2015-10-28
      • 2020-07-08
      • 2019-09-07
      • 2014-02-14
      • 2014-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-14
      相关资源
      最近更新 更多