Как создать повторяющийся генератор в Python

Как вы создаете повторяющийся генератор, например xrange, в Python? Например, если я это сделаю:

>>> 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 с выходом или с пониманием генератора? Я нашел обходной путь в проблеме Python tracker, в котором используется декоратор для преобразования генератора в итератор. Это перезапускается каждый раз, когда вы начинаете использовать его, даже если вы не использовали все значения в прошлый раз, точно так же, как 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? Так что мне не нужно качать свои классы и декораторы?

Update

Комментарий от u0b34a0f6ae прибил источник моего недоразумения:

xrange (5) не возвращает итератор, он создает объект xrange. Объекты xrange могут повторяться, как словари, более одного раза.

Моя "вечная" функция полностью лаяла по неправильному дереву, действуя как итератор/генератор (__iter__ возвращает self), а не как collection/xrange (__iter__ возвращает новый итератор).

Ответ 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)

Ответ 2

Если вы напишете много из них, ответ Джона Милликина будет самым чистым.

Но если вы не возражаете добавить 3 строки и некоторые отступы, вы можете сделать это без специального декоратора. Это составляет 2 трюка:

  • [В целом полезно:] Вы можете легко сделать класс итерабельным без реализации .next() - просто используйте генератор для __iter__(self)!

  • Вместо того, чтобы беспокоиться о конструкторе, вы можете определить одноразовый класс внутри функции.

= >

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

Малый шрифт: я не тестировал производительность, нерестные классы вроде этого могут быть расточительными. Но удивительно; -)

Ответ 3

Я думаю, что ответ на этот вопрос "Нет". Возможно, я ошибаюсь. Возможно, с некоторыми из надуманных новых вещей, которые вы можете сделать с генераторами в версии 2.6 с аргументами и обработкой исключений, которые позволят что-то вроде того, что вы хотите. Но эти функции в основном предназначены для реализации полупревращений.

Почему вы не хотите иметь свои собственные классы или декораторы? И почему вы хотели создать декоратор, который возвращал генератор вместо экземпляра класса?

Ответ 4

Использование 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!

Ответ 5

Итераторы reset с 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]

Примечание: потребление памяти растет при продвижении итератора, поэтому будьте осторожны, обертывая большие итерации.

Ответ 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]
>>>

Simple.