Как создать повторяющийся генератор в 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.