Expresiones del generador vs. Comprensión de lista

¿Cuándo debería usar expresiones generadoras y cuándo debería usar la comprensión de listas en Python?

# Generator expression (x*2 for x in range(256)) # List comprehension [x*2 for x in range(256)] 

La respuesta de John es buena (esa comprensión de la lista es mejor cuando se quiere iterar sobre algo varias veces). Sin embargo, también vale la pena señalar que debe utilizar una lista si desea utilizar cualquiera de los métodos de la lista. Por ejemplo, el siguiente código no funcionará:

 def gen(): return (something for something in get_some_stuff()) print gen()[:2] # generators don't support indexing or slicing print [5,6] + gen() # generators can't be added to lists 

Básicamente, use una expresión generadora si todo lo que está haciendo es iterar una vez. Si desea almacenar y usar los resultados generados, entonces probablemente esté mejor con una lista de comprensión.

Dado que el rendimiento es la razón más común para elegir uno sobre el otro, mi consejo es que no se preocupe por eso y simplemente elija uno; Si descubre que su progtwig se está ejecutando con demasiada lentitud, solo entonces debería regresar y preocuparse por ajustar su código.

Iterar sobre la expresión del generador o la comprensión de la lista hará lo mismo. Sin embargo, la lista de comprensión creará primero la lista completa en la memoria, mientras que la expresión del generador creará los elementos sobre la marcha, por lo que podrá usarlos para secuencias muy grandes (¡y también infinitas!).

Use la lista de comprensión cuando el resultado deba repetirse varias veces, o cuando la velocidad sea primordial. Use expresiones generadoras donde el rango sea grande o infinito.

El punto importante es que la lista de comprensión crea una nueva lista. El generador crea un objeto iterable que “filtrará” el material de origen sobre la marcha a medida que consume los bits.

Imagina que tienes un archivo de registro de 2TB llamado “hugefile.txt”, y quieres el contenido y la longitud de todas las líneas que comienzan con la palabra “ENTRY”.

Así que intenta comenzar escribiendo una lista de comprensión:

 logfile = open("hugefile.txt","r") entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")] 

Esto absorbe todo el archivo, procesa cada línea y almacena las líneas coincidentes en su matriz. Por lo tanto, esta matriz podría contener hasta 2 TB de contenido. Eso es un montón de RAM, y probablemente no sea práctico para sus propósitos.

Entonces, en cambio, podemos usar un generador para aplicar un “filtro” a nuestro contenido. No se lee ningún dato hasta que comencemos a iterar sobre el resultado.

 logfile = open("hugefile.txt","r") entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY")) 

Ni siquiera se ha leído una sola línea de nuestro archivo todavía. De hecho, digamos que queremos filtrar nuestro resultado aún más:

 long_entries = ((line,length) for (line,length) in entry_lines if length > 80) 

Aún no se ha leído nada, pero ahora hemos especificado dos generadores que actuarán sobre nuestros datos como deseamos.

Vamos a escribir nuestras líneas filtradas a otro archivo:

 outfile = open("filtered.txt","a") for entry,length in long_entries: outfile.write(entry) 

Ahora leemos el archivo de entrada. A medida que nuestro bucle for continúa solicitando líneas adicionales, el generador long_entries exige líneas del generador de entry_lines , devolviendo solo aquellas cuya longitud es superior a 80 caracteres. Y a su vez, el generador entry_lines solicita líneas (filtradas como se indica) desde el iterador del logfile , que a su vez lee el archivo.

Por lo tanto, en lugar de “enviar” datos a su función de salida en forma de una lista completa, le está dando a la función de salida una forma de “extraer” datos solo cuando es necesario. En nuestro caso, esto es mucho más eficiente, pero no tan flexible. Los generadores son unidireccionales, una pasada; los datos del archivo de registro que hemos leído se descartan inmediatamente, por lo que no podemos volver a una línea anterior. Por otro lado, no tenemos que preocuparnos por mantener los datos una vez que hayamos terminado con ellos.

El beneficio de una expresión generadora es que usa menos memoria, ya que no construye toda la lista a la vez. Las expresiones generadoras se utilizan mejor cuando la lista es un intermediario, como sumr los resultados o crear un dictado de los resultados.

Por ejemplo:

 sum(x*2 for x in xrange(256)) dict( ((k, some_func(k) for k in some_list_of_keys) ) 

La ventaja es que la lista no se genera completamente, y por lo tanto se usa poca memoria (y también debería ser más rápida)

Sin embargo, debe utilizar las listas de comprensión cuando el producto final deseado es una lista. No va a guardar ninguna memoria utilizando expresiones generadoras, ya que desea la lista generada. También obtiene la ventaja de poder utilizar cualquiera de las funciones de la lista como ordenadas o invertidas.

Por ejemplo:

 reversed( [x*2 for x in xrange(256)] ) 

Al crear un generador a partir de un objeto mutable (como una lista), tenga en cuenta que el generador se evaluará en el estado de la lista en el momento de usar el generador, no en el momento de la creación del generador:

 >>> mylist = ["a", "b", "c"] >>> gen = (elem + "1" for elem in mylist) >>> mylist.clear() >>> for x in gen: print (x) # nothing 

Si existe alguna posibilidad de que su lista se modifique (o un objeto mutable dentro de esa lista) pero necesita el estado en la creación del generador, necesita usar una lista de comprensión en su lugar.

Estoy usando el módulo Hadoop Mincemeat . Creo que este es un gran ejemplo para tomar nota de:

 import mincemeat def mapfn(k,v): for w in v: yield 'sum',w #yield 'count',1 def reducefn(k,v): r1=sum(v) r2=len(v) print r2 m=r1/r2 std=0 for i in range(r2): std+=pow(abs(v[i]-m),2) res=pow((std/r2),0.5) return r1,r2,res 

Aquí, el generador obtiene números de un archivo de texto (tan grande como 15GB) y aplica matemática simple en esos números usando el mapa reducido de Hadoop. Si no hubiera usado la función de rendimiento, sino una comprensión de la lista, habría tomado mucho más tiempo calcular las sums y el promedio (sin mencionar la complejidad del espacio).

Hadoop es un gran ejemplo para usar todas las ventajas de los generadores.

A veces, puede salirse con la función tee de itertools , devuelve múltiples iteradores para el mismo generador que se pueden usar de forma independiente.

¿qué hay de usar [(exp para x en iter)] para obtener el bien de ambos? Rendimiento de la comprensión del generador, así como los métodos de lista