Pregunta

¿Cuándo debería utilizar expresiones generadoras y cuándo debería utilizar listas por comprensión en Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
¿Fue útil?

Solución

La respuesta de John es buena (esa lista por comprensión es mejor cuando quieres repetir algo varias veces).Sin embargo, también vale la pena señalar que debe usar una lista si desea utilizar cualquiera de los métodos de 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, usa una expresión generadora si todo lo que estás haciendo es iterar una vez.Si desea almacenar y utilizar los resultados generados, probablemente sea mejor que utilice una lista por comprensión.

Dado que el rendimiento es la razón más común para elegir uno sobre otro, mi consejo es que no se preocupe y elija solo uno;Si descubre que su programa se ejecuta demasiado lento, entonces y sólo entonces debería volver atrás y preocuparse por ajustar su código.

Otros consejos

Iterando sobre el expresión generadora o el comprensión de la lista hará lo mismo.sin embargo, el comprensión de la lista creará primero la lista completa en la memoria mientras expresión generadora creará los elementos sobre la marcha, por lo que podrás usarlo para secuencias muy grandes (¡y también infinitas!).

Utilice listas por comprensión cuando el resultado deba repetirse varias veces o cuando la velocidad sea primordial.Utilice expresiones generadoras donde el rango sea grande o infinito.

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

Imagine que tiene un archivo de registro de 2 TB llamado "hugefile.txt" y desea conocer el contenido y la longitud de todas las líneas que comienzan con la palabra "ENTRY".

Entonces 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 sorbe 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 mucha RAM y probablemente no sea práctico para sus propósitos.

Entonces, en lugar de eso, podemos usar un generador para aplicar un "filtro" a nuestro contenido.En realidad, no se lee ningún dato hasta que comenzamos a iterar sobre el resultado.

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

Aún no se ha leído ni una sola línea de nuestro archivo.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 hemos especificado dos generadores que actuarán sobre nuestros datos como queramos.

Escribamos nuestras líneas filtradas en otro archivo:

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

Ahora leemos el archivo de entrada.Como el nuestro for El bucle continúa solicitando líneas adicionales, el long_entries El generador demanda líneas desde el entry_lines generador, devolviendo sólo aquellos cuya longitud sea superior a 80 caracteres.Y a su vez, el entry_lines El generador solicita líneas (filtradas como se indica) desde el logfile iterador, que a su vez lee el archivo.

Entonces, en lugar de "enviar" datos a su función de salida en forma de una lista completamente completa, le está dando a la función de salida una forma de "extraer" datos solo cuando sea necesario.En nuestro caso, esto es mucho más eficiente, pero no tan flexible.Los generadores son unidireccionales, de una sola 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 conservar los datos una vez que hayamos terminado con ellos.

El beneficio de una expresión generadora es que utiliza menos memoria ya que no crea la lista completa a la vez.Las expresiones generadoras se utilizan mejor cuando la lista es un intermediario, como sumar los resultados o crear un dictado a partir 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 utiliza poca memoria (y también debería ser más rápido)

Sin embargo, debe utilizar listas por comprensión cuando el producto final deseado sea una lista.No guardará ninguna memoria usando expresiones generadoras, ya que desea la lista generada.También obtiene el beneficio de poder utilizar cualquiera de las funciones de la lista, como ordenar o revertir.

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 será evaluado según el estado de la lista en el momento de utilizar 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 el momento de la creación del generador, debe usar una lista por comprensión en su lugar.

estoy usando el Módulo Hadoop carne picada.Creo que este es un gran ejemplo para tomar nota:

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 (de hasta 15 GB) y aplica matemáticas simples a esos números utilizando map-reduce de Hadoop.Si no hubiera usado la función de rendimiento, sino una lista de comprensión, me habría llevado mucho más tiempo calcular las sumas y el promedio (sin mencionar la complejidad del espacio).

Hadoop es un gran ejemplo del uso de todas las ventajas de los generadores.

A veces puedes salirte con la tuya tee función de herramientas iterativas, devuelve múltiples iteradores para el mismo generador que se pueden usar de forma independiente.

¿Qué tal si usamos [(exp for x in iter)] para obtener el bien de ambos?Rendimiento de la comprensión del generador y de los métodos de lista.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top