¿Cuándo debería usar expresiones generadoras y cuándo debería usar las comprensiones de listas en Python?
# Generator expression
(x*2 for x in range(256))
# List comprehension
[x*2 for x in range(256)]
python
list-comprehension
generator
Solo lectura
fuente
fuente
[exp for x in iter]
ser solo azúcarlist((exp for x in iter))
? o hay una diferencia de ejecución?X = [x**2 for x in range(5)]; print x
conY = list(y**2 for y in range(5)); print y
, el segundo dará un error. En Python3, una comprensión de la lista es, de hecho, el azúcar sintáctico para una expresión generadora alimentadalist()
como esperaba, por lo que la variable del bucle ya no se filtrará .Respuestas:
La respuesta de John es buena (esa comprensión de la lista es mejor cuando quieres iterar sobre algo varias veces). Sin embargo, también vale la pena señalar que debe usar una lista si desea usar cualquiera de los métodos de la lista. Por ejemplo, el siguiente código no funcionará:
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 sea mejor con una comprensión de la lista.
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 encuentra que su programa se está ejecutando demasiado lento, entonces y solo entonces debería regresar y preocuparse por ajustar su código.
fuente
a = [1, 2, 3] b = [4, 5, 6] a.extend(b)
- a ahora será [1, 2, 3, 4, 5, 6]. (¿Puedes agregar nuevas líneas en los comentarios?)a = (x for x in range(0,10)), b = [1,2,3]
por ejemplo.a.extend(b)
lanza una excepción.b.extend(a)
evaluará todo a, en cuyo caso no tiene sentido convertirlo en un generador en primer lugar.Iterar sobre la expresión del generador o la comprensión de la lista hará lo mismo. Sin embargo, la comprensión de la lista creará la lista completa en la memoria primero, mientras que la expresión del generador creará los elementos sobre la marcha, por lo que puede usarla para secuencias muy grandes (¡y también infinitas!).
fuente
itertools.count(n)
es una secuencia infinita de enteros, comenzando desde n, por(2 ** item for item in itertools.count(n))
lo que sería una secuencia infinita de los poderes de2
comenzar en2 ** n
.Utilice las comprensiones de listas cuando el resultado deba repetirse varias veces o cuando la velocidad sea primordial. Use expresiones generadoras donde el rango sea grande o infinito.
Consulte Expresiones de generador y comprensiones de listas para obtener más información.
fuente
lists
, ¿ son más rápidos que lasgenerator
expresiones? Al leer la respuesta de dF, me di cuenta de que era al revés.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 de origen sobre la marcha a medida que consume los bits.
Imagine que tiene un archivo de registro de 2TB llamado "hugefile.txt", y desea 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:
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 mucha RAM, y probablemente no sea práctico para sus propósitos.
Por lo tanto, podemos usar un generador para aplicar un "filtro" a nuestro contenido. En realidad, no se leen datos hasta que comenzamos a iterar sobre el resultado.
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:
Todavía no se ha leído nada, pero ahora hemos especificado dos generadores que actuarán sobre nuestros datos como lo deseamos.
Vamos a escribir nuestras líneas filtradas a otro archivo:
Ahora leemos el archivo de entrada. A medida que nuestro
for
bucle continúa solicitando líneas adicionales, ellong_entries
generador exige líneas delentry_lines
generador, devolviendo solo aquellas cuya longitud es mayor a 80 caracteres. Y a su vez, elentry_lines
generador solicita líneas (filtradas como se indica) dellogfile
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 poblada, le está dando a la función de salida una forma de "extraer" datos solo cuando sea necesario. Esto es en nuestro caso mucho más eficiente, pero no tan flexible. Los generadores son unidireccionales, un paso; los datos del archivo de registro que hemos leído se descartan de inmediato, 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.
fuente
El beneficio de una expresión generadora es que usa menos memoria ya que no construye la lista completa a la vez. Las expresiones generadoras se usan mejor cuando la lista es un intermediario, como sumar los resultados o crear un dict a partir de los resultados.
Por ejemplo:
La ventaja es que la lista no se genera por completo y, por lo tanto, se usa poca memoria (y también debería ser más rápida)
Sin embargo, debe utilizar la comprensión de la lista 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 el beneficio de poder utilizar cualquiera de las funciones de la lista, como ordenado o invertido.
Por ejemplo:
fuente
sum(x*2 for x in xrange(256))
sorted
yreversed
funciona bien en cualquier iterador, expresiones generadoras incluidas.Al crear un generador a partir de un objeto mutable (como una lista), tenga en cuenta que el generador será evaluado en el estado de la lista al momento de usar el generador, no en el momento de la creación del generador:
Si hay 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 comprensión de la lista.
fuente
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.
fuente
Estoy usando el módulo Hadoop Mincemeat . Creo que este es un gran ejemplo para tomar nota de:
Aquí, el generador obtiene números de un archivo de texto (de hasta 15 GB) y aplica cálculos matemáticos simples a esos números usando el mapa de reducción de Hadoop. Si no hubiera utilizado la función de rendimiento, sino una comprensión de la lista, 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 para utilizar todas las ventajas de los generadores.
fuente