Pregunta

Esto es más bien lo contrario de ¿Qué puede usar las funciones del generador Python? para? : los generadores de python, las expresiones generadoras y el módulo itertools 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 pila de datos. A menudo los uso al procesar archivos DSV.

Entonces, ¿cuándo es no un buen momento para usar un generador, una expresión de generador o una función de itertools ?

  • ¿Cuándo debería preferir zip () sobre itertools.izip () , o
  • range () sobre xrange () , o
  • [x para x en foo] sobre (x para x en foo) ?

Obviamente, al final necesitamos " resolver " un generador en datos reales, generalmente creando una lista o iterándola con un bucle no generador. A veces solo necesitamos saber la longitud. Esto no es lo que estoy preguntando.

Utilizamos generadores para no asignar nuevas listas a la memoria para datos provisionales. Esto especialmente tiene sentido para grandes conjuntos de datos. ¿Tiene sentido también para conjuntos de datos pequeños? ¿Hay un intercambio de memoria / CPU notable?

Estoy especialmente interesado si alguien ha hecho algún análisis de esto, a la luz de la discusión reveladora de muestra el rendimiento de la comprensión en comparación con map () y filter () . ( enlace alternativo )

¿Fue útil?

Solución

Use una lista en lugar de un generador cuando:

1) Debe acceder a los datos múltiples 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 otro acceso que no sea el orden secuencial de reenvío):

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) Debes unir cadenas (que requieren dos pasadas sobre los datos):

s = ''.join(data)                # lists are faster than generators in this use case

4) Estás usando PyPy que a veces no puede optimizar el código del generador tanto como puede con las llamadas de función normales y las manipulaciones de listas.

Otros consejos

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 que pueda liberar un recurso). En ese caso, una expresión de lista podría ser mejor.

Perfil, Perfil, Perfil.

Perfilar tu código es la única forma de saber si lo que estás haciendo tiene algún efecto.

La mayoría de los usos de xrange, generadores, etc. son sobre tamaño estático, pequeños conjuntos de datos. Es solo cuando se llega a grandes conjuntos de datos que realmente hace una diferencia. range () vs. xrange () es principalmente una cuestión de hacer que el código se vea un poco más feo, y no perder nada, y tal vez ganar algo.

Perfil, Perfil, Perfil.

Nunca debe favorecer zip sobre izip , < código> rango sobre xrange , o enumerar las comprensiones sobre las comprensiones del generador. En Python 3.0 range tiene xrange -como la semántica y zip tiene izip -como la semántica.

Las

listas de comprensión son más claras, como list (frob (x) para x in foo) para aquellos momentos en que necesite una lista real.

Como mencionaste, " Esto especialmente tiene sentido para grandes conjuntos de datos " ;, creo que esto responde a tu pregunta.

Si no golpeas ningún muro, en lo que respecta al rendimiento, puedes seguir usando listas y funciones estándar. Luego, cuando tenga problemas con el rendimiento, haga el cambio.

Sin embargo, como lo mencionó @ u0b34a0f6ae en los comentarios, el uso de generadores al inicio puede hacer que sea más fácil escalar a conjuntos de datos más grandes.

Respecto al rendimiento: si usa psyco, las listas pueden ser un poco más rápidas que los generadores. En el ejemplo a continuación, 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

En lo que respecta al rendimiento, no puedo pensar en ningún momento en el que desee utilizar una lista en un generador.

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 si no los usara.

Por ejemplo:

sorted(xrange(5))

No ofrece ninguna mejora con respecto a:

sorted(range(5))

Debería preferir listas de 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 repetirá varias veces más tarde en su programa.

Hasta cierto punto, puede pensar en los generadores como un reemplazo de la iteración (bucles) en comparación con las comprensiones de la lista como un tipo de inicialización de la estructura de datos. Si desea mantener la estructura de datos, utilice la lista de comprensión.

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