2011-02-09 12 views
37

Considere este escenario:¿Cómo clonar un objeto generador de Python?

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
import os 

walk = os.walk('/home') 

for root, dirs, files in walk: 
    for pathname in dirs+files: 
     print os.path.join(root, pathname) 

for root, dirs, files in walk: 
    for pathname in dirs+files: 
     print os.path.join(root, pathname)

Sé que este ejemplo es un poco redundante, pero se debe considerar que tenemos que utilizar los mismos datos walk más de una vez. Tengo un escenario de referencia y el uso de los mismos datos walk es obligatorio para obtener resultados útiles.

He intentado clonar y usar walk2 = walk en la segunda iteración, pero no funcionó. La pregunta es ... ¿Cómo puedo copiarlo? ¿Es alguna vez posible?

Gracias de antemano.

+0

¿Qué tiene de malo usar 'os.walk ('/ home')' dos veces? cuál es el problema? –

+2

@ S.Lott Bueno, ese tipo de tarea varía mucho en cada ejecución. Otro problema es que después de la primera ejecución el sistema probablemente almacenará en caché los resultados, por lo que en las próximas ejecuciones obtendremos resultados poco precisos. La idea es caminar antes y luego medir dos escenarios pasándolo como argumento. :) –

+0

El almacenamiento en caché no causará resultados falsos. –

Respuesta

54

Puede utilizar itertools.tee():

walk, walk2 = itertools.tee(walk) 

Tenga en cuenta que esto podría "necesidad significativa de almacenamiento adicional", ya que la documentación señala.

+6

también, la [documentación] (http://docs.python.org/2/library/itertools.html#itertools.tee) dice: "En general, si un iterador usa la mayoría o la totalidad de los datos antes de que comience otro iterador , es más rápido usar 'list()' en lugar de 'tee()'. " Teniendo en cuenta que el fragmento de código original de OP se repite una vez completamente, y luego de nuevo, ¿no se le recomendaría usar 'list()'? – HorseloverFat

+0

Use un generador en caché en su lugar, por ejemplo con 'lambda: a_new_generator', como se describe [aquí] (http://stackoverflow.com/a/21315536/1959808). –

+1

Vea también los comentarios a [esta respuesta] (http://stackoverflow.com/a/1271481/1959808). –

4

Definir una función

def walk_home(): 
    for r in os.walk('/home'): 
     yield r 

O incluso este

def walk_home(): 
    return os.walk('/home') 

Ambos se utilizan como esto:

for root, dirs, files in walk_home(): 
    for pathname in dirs+files: 
     print os.path.join(root, pathname) 
+1

Aunque no es la respuesta a la pregunta exacta que hizo el OP, esta es una buena manera de hacerlo sin almacenar el árbol de directorios completo en la memoria. +1 –

+3

El bucle es innecesario. 'def walk_home(): return os.walk ('/ home')' hace lo mismo. – shang

+0

@Sven Marnach: La pregunta "exacta" tiene poco sentido. –

12

Si sabe que va a recorrer todo el generador para cada uso, probablemente obtenga el mejor rendimiento desenrollando el generador a una lista y usando la lista varias veces.

walk = list(os.walk('/home'))

1

Esta respuesta tiene como objetivo extender/elaborada sobre lo que los otros han expresado respuestas. La solución variará necesariamente según lo que exactamente desee lograr.

Si desea iterar sobre el mismo resultado exacto de os.walk varias veces, se necesita para inicializar una lista de la os.walk artículos de iterables (es decir walk = list(os.walk(path))).

Si debe garantizar que los datos siguen siendo los mismos, esa es probablemente su única opción. Sin embargo, hay varios escenarios en los que esto no es posible o deseable.

  1. No será posible list() un iterable si la salida es de tamaño suficiente (es decir, el intento de list() un sistema de ficheros entero puede congelar el ordenador).
  2. No es deseable list() iterable si desea adquirir datos "nuevos" antes de cada uso.

En caso de que list() no sea adecuado, deberá ejecutar su generador bajo demanda. Tenga en cuenta que los generadores se extinguen después de cada uso, por lo que esto plantea un ligero problema.Con el fin de "volver a ejecutar su generador" en múltiples ocasiones, puede utilizar el siguiente patrón:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
import os 

class WalkMaker: 
    def __init__(self, path): 
     self.path = path 
    def __iter__(self): 
     for root, dirs, files in os.walk(self.path): 
      for pathname in dirs + files: 
       yield os.path.join(root, pathname) 

walk = WalkMaker('/home') 

for path in walk: 
    pass 

# do something... 

for path in walk: 
    pass 

El patrón de diseño antes mencionado le permitirá mantener seca su código.