Esto es más bien lo inverso de ¿Para qué se pueden usar las funciones del generador de Python? : los generadores de Python, las expresiones del generador y el itertools
módulo son algunas de mis características favoritas de Python en estos días. Son especialmente útiles al configurar cadenas de operaciones para realizar en una gran cantidad de datos; a menudo los uso cuando proceso archivos DSV.
Entonces, ¿cuándo no es un buen momento para usar un generador, o una expresión generadora, o una itertools
función?
- Cuando debería preferir
zip()
másitertools.izip()
, o range()
terminadoxrange()
, o[x for x in foo]
terminado(x for x in foo)
?
Obviamente, eventualmente necesitamos "resolver" un generador en datos reales, generalmente creando una lista o iterando sobre ella con un ciclo que no sea generador. A veces solo necesitamos saber la longitud. Esto no es lo que estoy preguntando.
Usamos generadores para no asignar nuevas listas a la memoria para datos provisionales. Esto tiene sentido especialmente para grandes conjuntos de datos. ¿Tiene sentido también para pequeños conjuntos de datos? ¿Existe una compensación notable entre memoria y CPU?
Estoy especialmente interesado si alguien ha elaborado algunos perfiles sobre esto, a la luz de la discusión reveladora sobre el rendimiento de comprensión de listas frente a map () y filter () . ( enlace alternativo )
fuente
<5
.Respuestas:
Utilice una lista en lugar de un generador cuando:
1) Debe acceder a los datos varias veces (es decir, almacenar en caché los resultados en lugar de volver a calcularlos):
for i in outer: # used once, okay to be a generator or return a list for j in inner: # used multiple times, reusing a list is better ...
2) Necesita acceso aleatorio (o cualquier acceso que no sea el orden secuencial hacia adelante):
for i in reversed(data): ... # generators aren't reversible s[i], s[j] = s[j], s[i] # generators aren't indexable
3) Debe unir cadenas (lo que requiere dos pasadas sobre los datos):
s = ''.join(data) # lists are faster than generators in this use case
4) Está utilizando PyPy, que a veces no puede optimizar el código del generador tanto como puede con las llamadas a funciones normales y las manipulaciones de listas.
fuente
ireduce
para replicar la combinación?''.join('%s' % i for i in xrange(10))
En general, no use un generador cuando necesite operaciones de lista, como len (), reversed (), etc.
También puede haber ocasiones en las que no desee una evaluación perezosa (por ejemplo, hacer todo el cálculo por adelantado para poder liberar un recurso). En ese caso, una expresión de lista podría ser mejor.
fuente
Perfil, perfil, perfil.
Perfilar su código es la única forma de saber si lo que está haciendo tiene algún efecto.
La mayoría de los usos de xrange, generators, etc. son de tamaño estático, pequeños conjuntos de datos. Es solo cuando se llega a grandes conjuntos de datos que realmente se marca la diferencia. range () vs xrange () es principalmente una cuestión de hacer que el código se vea un poquito más feo, y no perder nada, y tal vez ganar algo.
Perfil, perfil, perfil.
fuente
Nunca se debe favorecer
zip
másizip
,range
másxrange
o listas por comprensión más comprensiones del generador. En Python 3.0range
tienexrange
-como la semántica yzip
tieneizip
-como la semántica.Las
list(frob(x) for x in foo)
listas por comprensión son en realidad más claras, como en las ocasiones en que necesita una lista real.fuente
for
bucles expandidos !), Pero se pueden escribir fácilmente listas por comprensión incomprensibles.[]
forma lo suficientemente descriptiva (y más concisa, y menos desordenada, en general). Pero esto es solo una cuestión de gustos.Como mencionas, "Esto tiene sentido especialmente para grandes conjuntos de datos", creo que esto responde a tu pregunta.
Si no golpea ninguna pared, en cuanto al rendimiento, aún puede atenerse a las listas y funciones estándar. Luego, cuando tenga problemas con el rendimiento, haga el cambio.
Sin embargo, como lo menciona @ u0b34a0f6ae en los comentarios, el uso de generadores al principio puede hacer que sea más fácil escalar a conjuntos de datos más grandes.
fuente
En cuanto al rendimiento: si usas psyco, las listas pueden ser un poco más rápidas que los generadores. En el siguiente ejemplo, las listas son casi un 50% más rápidas cuando se usa psyco.full ()
import psyco import time import cStringIO def time_func(func): """The amount of time it requires func to run""" start = time.clock() func() return time.clock() - start def fizzbuzz(num): """That algorithm we all know and love""" if not num % 3 and not num % 5: return "%d fizz buzz" % num elif not num % 3: return "%d fizz" % num elif not num % 5: return "%d buzz" % num return None def with_list(num): """Try getting fizzbuzz with a list comprehension and range""" out = cStringIO.StringIO() for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]: print >> out, fibby return out.getvalue() def with_genx(num): """Try getting fizzbuzz with generator expression and xrange""" out = cStringIO.StringIO() for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)): print >> out, fibby return out.getvalue() def main(): """ Test speed of generator expressions versus list comprehensions, with and without psyco. """ #our variables nums = [10000, 100000] funcs = [with_list, with_genx] # try without psyco 1st print "without psyco" for num in nums: print " number:", num for func in funcs: print func.__name__, time_func(lambda : func(num)), "seconds" print # now with psyco print "with psyco" psyco.full() for num in nums: print " number:", num for func in funcs: print func.__name__, time_func(lambda : func(num)), "seconds" print if __name__ == "__main__": main()
Resultados:
without psyco number: 10000 with_list 0.0519102208309 seconds with_genx 0.0535933367509 seconds number: 100000 with_list 0.542204280744 seconds with_genx 0.557837353115 seconds with psyco number: 10000 with_list 0.0286369007033 seconds with_genx 0.0513424889137 seconds number: 100000 with_list 0.335414877839 seconds with_genx 0.580363490491 seconds
fuente
En lo que respecta al rendimiento, no puedo pensar en ningún momento en el que desee utilizar una lista en lugar de un generador.
fuente
all(True for _ in range(10 ** 8))
es más lento queall([True for _ in range(10 ** 8)])
en Python 3.8. Preferiría una lista sobre un generador aquíNunca he encontrado una situación en la que los generadores obstaculicen lo que intentas hacer. Sin embargo, hay muchos casos en los que el uso de generadores no le ayudaría más que no usarlos.
Por ejemplo:
sorted(xrange(5))
No ofrece ninguna mejora sobre:
sorted(range(5))
fuente
range(5)
, ya que la lista resultante ya está ordenada.Debería preferir listas por comprensión si necesita mantener los valores para otra cosa más adelante y el tamaño de su conjunto no es demasiado grande.
Por ejemplo: está creando una lista que recorrerá varias veces más adelante en su programa.
Hasta cierto punto, puede pensar en los generadores como un reemplazo de la iteración (bucles) frente a las listas por comprensión como un tipo de inicialización de la estructura de datos. Si desea mantener la estructura de datos, utilice listas por comprensión.
fuente
itertools.tee()
pueda ayudarlo. Pero en general, si desea más de una pasada, o acceso aleatorio a algunos datos intermedios, haga una lista / conjunto / dictado.