Tengo un objeto generador devuelto por rendimiento múltiple. La preparación para llamar a este generador es una operación bastante lenta. Es por eso que quiero reutilizar el generador varias veces.
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
Por supuesto, estoy teniendo en cuenta copiar el contenido en una lista simple. ¿Hay alguna manera de restablecer mi generador?
y = list(y)
con el resto de su código sin cambios.Los generadores no se pueden rebobinar. Tienes las siguientes opciones:
Ejecute la función de generador nuevamente, reiniciando la generación:
Almacene los resultados del generador en una estructura de datos en la memoria o en el disco que puede repetir nuevamente:
La desventaja de la opción 1 es que vuelve a calcular los valores. Si eso requiere mucha CPU, terminas calculando dos veces. Por otro lado, la desventaja de 2 es el almacenamiento. La lista completa de valores se almacenará en la memoria. Si hay demasiados valores, eso puede ser poco práctico.
Entonces tienes el clásico memoria frente a la compensación de procesamiento . No puedo imaginar una forma de rebobinar el generador sin almacenar los valores o calcularlos nuevamente.
fuente
fuente
Probablemente la solución más simple es envolver la parte costosa en un objeto y pasarla al generador:
De esta manera, puede almacenar en caché los costosos cálculos.
Si puede mantener todos los resultados en la RAM al mismo tiempo, utilice
list()
para materializar los resultados del generador en una lista simple y trabaje con eso.fuente
Quiero ofrecer una solución diferente a un viejo problema.
El beneficio de esto cuando se compara con algo así
list(iterator)
es que esto esO(1)
complejidad espacial y lolist(iterator)
esO(n)
. La desventaja es que, si solo tiene acceso al iterador, pero no a la función que produjo el iterador, entonces no puede usar este método. Por ejemplo, puede parecer razonable hacer lo siguiente, pero no funcionará.fuente
Si la respuesta de GrzegorzOledzki no es suficiente, probablemente podría utilizar
send()
para lograr su objetivo. Consulte PEP-0342 para obtener más detalles sobre generadores mejorados y expresiones de rendimiento.ACTUALIZACIÓN: Ver también
itertools.tee()
. Involucra parte de ese intercambio de memoria versus procesamiento mencionado anteriormente, pero puede ahorrar algo de memoria en lugar de almacenar los resultados del generador en alist
; depende de cómo estés usando el generador.fuente
Si su generador es puro en el sentido de que su salida solo depende de argumentos pasados y el número de paso, y desea que el generador resultante sea reiniciable, aquí hay un fragmento de clasificación que podría ser útil:
salidas:
fuente
De la documentación oficial de tee :
Por lo tanto, es mejor usarlo
list(iterable)
en su caso.fuente
list()
pone todo el iterable en memoriatee()
si un iterador consume todos los valores, así es comotee
funciona.Usar una función de contenedor para manejar
StopIteration
Podría escribir una función envoltura simple en su función generadora de generador que rastrea cuando el generador está agotado. Lo hará utilizando la
StopIteration
excepción que arroja un generador cuando llega al final de la iteración.Como puede ver arriba, cuando nuestra función de contenedor detecta una
StopIteration
excepción, simplemente reinicia el objeto generador (utilizando otra instancia de la llamada a la función).Y luego, suponiendo que defina su función de suministro de generador en algún lugar como se muestra a continuación, podría usar la sintaxis del decorador de funciones de Python para envolverla implícitamente:
fuente
Puede definir una función que devuelva su generador
Ahora puedes hacer tantas veces como quieras:
fuente
No estoy seguro de qué querías decir con preparación costosa, pero supongo que realmente tienes
Si ese es el caso, ¿por qué no reutilizar
data
?fuente
No hay opción para restablecer iteradores. El iterador generalmente aparece cuando itera a través de la
next()
función. La única forma es hacer una copia de seguridad antes de iterar en el objeto iterador. Verifique a continuación.Crear objeto iterador con elementos del 0 al 9
Iterando a través de la función next () que aparecerá
Convertir el objeto iterador a la lista
entonces el elemento 0 ya está desplegado. Además, todos los elementos aparecen cuando convertimos el iterador a la lista.
Por lo tanto, debe convertir el iterador en listas para la copia de seguridad antes de comenzar a iterar. La lista podría convertirse a iterador con
iter(<list-object>)
fuente
Ahora puede usar
more_itertools.seekable
(una herramienta de terceros) que permite restablecer iteradores.Instalar a través de
> pip install more_itertools
Nota: el consumo de memoria crece mientras avanza el iterador, así que tenga cuidado con los iterables grandes.
fuente
Puede hacerlo utilizando itertools.cycle () , puede crear un iterador con este método y luego ejecutar un bucle for sobre el iterador que recorrerá sus valores.
Por ejemplo:
generará 20 números, de 0 a 4 repetidamente.
Una nota de los documentos:
fuente
Ok, dices que quieres llamar a un generador varias veces, pero la inicialización es costosa ... ¿Qué tal algo como esto?
Alternativamente, puede crear su propia clase que siga el protocolo iterador y defina algún tipo de función de 'restablecimiento'.
https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html
fuente
__call__
Mi respuesta resuelve un problema ligeramente diferente: si el generador es costoso de inicializar y cada objeto generado es costoso de generar. Pero necesitamos consumir el generador varias veces en múltiples funciones. Para llamar al generador y a cada objeto generado exactamente una vez, podemos usar subprocesos y ejecutar cada uno de los métodos de consumo en diferentes subprocesos. Es posible que no logremos un verdadero paralelismo debido a GIL, pero lograremos nuestro objetivo.
Este enfoque hizo un buen trabajo en el siguiente caso: el modelo de aprendizaje profundo procesa muchas imágenes. El resultado es una gran cantidad de máscaras para muchos objetos en la imagen. Cada máscara consume memoria. Tenemos alrededor de 10 métodos que hacen diferentes estadísticas y métricas, pero toman todas las imágenes a la vez. Todas las imágenes no pueden caber en la memoria. Los métodos pueden reescribirse fácilmente para aceptar el iterador.
Uso:
fuente
itertools.islice
o para asíncronoaiostream.stream.take
, y esta publicación le permite hacerlo de forma asinosa / espera stackoverflow.com/a/42379188/149818Se puede hacer por objeto de código. Aquí está el ejemplo.
1 2 3 4
1 2 3 4
fuente
exec
que es un poco no recomendado para un caso tan simple.