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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1376438/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 22:05:11  来源:igfitidea点击:

How to make a repeating generator in Python

python

提问by Alice Purcell

How do you make a repeating generator, like xrange, in Python? For instance, if I do:

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

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

I get the same result both times — the numbers 0..4. However, if I try the same with yield:

我两次得到相同的结果——数字 0..4。但是,如果我对 yield 尝试相同的方法:

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

The second time I try to iterate over m, I get nothing back — an empty list.

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

Is there a simple way to create a repeating generator like xrange with yield, or generator comprehensions? I found a workaround on a Python tracker issue, which uses a decorator to transform a generator into an iterator. This restarts every time you start using it, even if you didn't use all the values last time through, just like xrange. I also came up with my own decorator, based on the same idea, which actually returns a generator, but one which can restart after throwing a StopIteration exception:

有没有一种简单的方法来创建一个重复的生成器,比如带产量的 xrange 或生成器理解?我在 Python 跟踪器问题上找到了一个解决方法,它使用装饰器将生成器转换为迭代器。每次开始使用它时都会重新启动,即使上次没有使用所有值,就像 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()

Is there a better way to solve the problem, using only yield and/or generator comprehensions? Or something built into Python? So I don't need to roll my own classes and decorators?

有没有更好的方法来解决这个问题,只使用 yield 和/或 generator comprehensions?或者 Python 内置的东西?所以我不需要滚动我自己的类和装饰器?

Update

更新

The comment by u0b34a0f6aenailed the source of my misunderstanding:

u0b34a0f6ae评论指出了我误解的根源:

xrange(5) does not return an iterator, it creates an xrange object. xrange objects can be iterated, just like dictionaries, more than once.

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

My "eternal" function was barking up the wrong tree entirely, by acting like an iterator/generator (__iter__returns self) rather than like a collection/xrange (__iter__returns a new iterator).

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

采纳答案by John Millikin

Not directly. Part of the flexibility that allows generators to be used for implementing co-routines, resource management, etc, is that they are always one-shot. Once run, a generator cannot be re-run. You would have to create a new generator object.

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

However, you can create your own class which overrides __iter__(). It will act like a reusable generator:

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

回答by Matt S

Using itertools its super easy.

使用 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!

回答by Beni Cherniavsky-Paskin

If you write a lot of these, John Millikin's answer is the cleanest it gets.

如果你写了很多这些,约翰米利金的答案是最干净的。

But if you don't mind adding 3 lines and some indentation, you can do it without a custom decorator. This composes 2 tricks:

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

  1. [Generally useful:] You can easily make a class iterable without implementing .next()- just use a generator for __iter__(self)!

  2. Instead of bothering with a constructor, you can define a one-off class inside a function.

  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()

Small print: I didn't test performance, spawning classes like this might be wasteful. But awesome ;-)

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

回答by Omnifarious

I think the answer to that is "No". I'm possibly wrong. It may be that with some of the funky new things you can do with generators in 2.6 involving arguments and exception handling that would allow something like what you want. But those features are mostly intended for implementing semi-continuations.

我认为答案是否定的。我可能错了。可能是因为你可以用 2.6 中的生成器做一些时髦的新东西,涉及参数和异常处理,这将允许你想要的东西。但这些功能主要用于实现半延续。

Why do you want to not have your own classes or decorators? And why did you want to create a decorator that returned a generator instead of a class instance?

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

回答by pylang

You can reset iterators with more_itertools.seekable, a third-party tool.

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

Install via > pip install more_itertools.

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

Note: memory consumption grows while advancing an iterator, so be wary wrapping large iterables.

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

回答by SmartElectron

use this solution:

使用此解决方案:

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

and with a decorator:

并带有装饰器:

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

Simple.

简单的。