Cómo hacer un generador de repetición en Python

¿Cómo se hace un generador de repetición, como xrange, en Python? Por ejemplo, si lo hago:

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

Consigo el mismo resultado en ambas ocasiones: los números 0..4. Sin embargo, si bash lo mismo con el rendimiento:

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

La segunda vez que trato de iterar sobre m, no obtengo nada, una lista vacía.

¿Hay una forma sencilla de crear un generador de repetición como xrange con rendimiento o comprensión de generador? Encontré una solución alternativa al problema del rastreador de Python , que utiliza un decorador para transformar un generador en un iterador. Esto se reinicia cada vez que empiezas a usarlo, incluso si no usaste todos los valores la última vez, como xrange. También se me ocurrió mi propio decorador, basado en la misma idea, que en realidad devuelve un generador, pero que se puede reiniciar después de lanzar una excepción 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() 

¿Hay una mejor manera de resolver el problema, usando solo el rendimiento y / o las comprensiones del generador? ¿O algo incorporado en Python? ¿Entonces no necesito rodar mis propias clases y decoradores?

Actualizar

El comentario de u0b34a0f6ae detectó la fuente de mi malentendido:

xrange (5) no devuelve un iterador, crea un objeto xrange. Los objetos xrange se pueden iterar, al igual que los diccionarios, más de una vez.

Mi función “eterna” estaba ladrando al árbol equivocado por completo, actuando como un iterador / generador ( __iter__ devuelve self) en lugar de como una colección / xrange ( __iter__ devuelve un nuevo iterador).

No directamente. Parte de la flexibilidad que permite que los generadores se utilicen para implementar co-rutinas, administración de recursos, etc., es que siempre son de una sola vez. Una vez que se ejecuta, un generador no se puede volver a ejecutar. Tendrías que crear un nuevo objeto generador.

Sin embargo, puede crear su propia clase que anula __iter__() . Actuará como un generador reutilizable:

 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) 

Usando itertools es muy fácil.

 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! 

Si escribes muchos de estos, la respuesta de John Millikin es lo más limpio que se pone.

Pero si no te importa agregar 3 líneas y algo de sangría, puedes hacerlo sin un decorador personalizado. Esto compone 2 trucos:

  1. [Generalmente útil:] Usted puede fácilmente hacer una clase iterable sin implementar .next() – ¡solo use un generador para __iter__(self) !

  2. En lugar de molestarse con un constructor, puede definir una clase única dentro de una función.

=>

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

Letra pequeña: no probé el rendimiento, las clases de desove como esta podrían ser un desperdicio. Pero impresionante 😉

Creo que la respuesta a eso es “No”. Posiblemente me equivoque Puede ser que con algunas de las cosas nuevas y divertidas que puede hacer con los generadores en la versión 2.6, que incluyan argumentos y manejo de excepciones, permita algo como lo que desea. Pero esas características están destinadas principalmente a la implementación de semi-continuaciones.

¿Por qué no quieres tener tus propias clases o decoradores? ¿Y por qué quiso crear un decorador que devolviera un generador en lugar de una instancia de clase?

Puede restablecer los iteradores con more_itertools.seekable , una herramienta de terceros.

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

Nota: el consumo de memoria aumenta a medida que avanza un iterador, así que tenga cuidado al envolver grandes iterables.

Usa esta solución:

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

y con un decorador:

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

Sencillo.