Python: generador de expresión vs rendimiento

En Python, ¿hay alguna diferencia entre la creación de un generador de objetos a través de un generador de expresión versus el uso de la rendimiento declaración?

Utilizando rendimiento:

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

Utilizando generador de expresión:

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

Ambas funciones devuelven generador de objetos, que producen las tuplas, por ejemplo, (0,0), (0,1), etc.

Las ventajas de uno u otro? Los pensamientos?


Gracias a todos! Hay una gran cantidad de información y más referencias en estas respuestas!

  • Elija la que le resulte más legible.
InformationsquelleAutor cschol | 2010-01-03

8 Kommentare

  1. 70

    Sólo hay pequeñas diferencias en los dos. Usted puede utilizar el dis módulo para examinar este tipo de cosas por ti mismo.

    Edición: Mi primera versión descompilar el generador de expresión creado en el módulo de ámbito interactivo de símbolo del sistema. Que es ligeramente diferente de la OP de la versión con la que se utiliza dentro de una función. He modificado este para que coincida con el caso real en la pregunta.

    Como se puede ver a continuación, el «rendimiento» del generador (primer caso) tiene tres instrucciones adicionales en la instalación, pero desde el primer FOR_ITER difieren sólo en un aspecto: el «rendimiento» enfoque utiliza un LOAD_FAST en lugar de un LOAD_DEREF dentro del bucle. El LOAD_DEREF es «más lento» de LOAD_FAST, por lo que hace que el «rendimiento» de la versión ligeramente más rápido que el generador de expresión lo suficientemente grandes valores de x (el bucle externo) porque el valor de y se carga un poco más rápido en cada paso. Para valores más pequeños de x sería un poco más lento debido a la sobrecarga adicional de la instalación de código.

    También podría ser la pena señalar que el generador de expresión suele ser usada en línea en el código, en lugar de hacerlo con la función de que. Que le quite un poco de la configuración de sobrecarga y mantener el generador expresión ligeramente más rápido para el lazo más pequeño de los valores, incluso si LOAD_FAST dio el «rendimiento» de la versión de una ventaja de otra manera.

    En ninguno de los casos sería la diferencia de rendimiento será suficiente para justificar la decisión entre uno u otro. La legibilidad cuenta mucho más, por lo que el uso de lo que se siente más legible para la situación actual.

    >>> def Generator(x, y):
    ...     for i in xrange(x):
    ...         for j in xrange(y):
    ...             yield(i, j)
    ...
    >>> dis.dis(Generator)
      2           0 SETUP_LOOP              54 (to 57)
                  3 LOAD_GLOBAL              0 (xrange)
                  6 LOAD_FAST                0 (x)
                  9 CALL_FUNCTION            1
                 12 GET_ITER
            >>   13 FOR_ITER                40 (to 56)
                 16 STORE_FAST               2 (i)
    
      3          19 SETUP_LOOP              31 (to 53)
                 22 LOAD_GLOBAL              0 (xrange)
                 25 LOAD_FAST                1 (y)
                 28 CALL_FUNCTION            1
                 31 GET_ITER
            >>   32 FOR_ITER                17 (to 52)
                 35 STORE_FAST               3 (j)
    
      4          38 LOAD_FAST                2 (i)
                 41 LOAD_FAST                3 (j)
                 44 BUILD_TUPLE              2
                 47 YIELD_VALUE
                 48 POP_TOP
                 49 JUMP_ABSOLUTE           32
            >>   52 POP_BLOCK
            >>   53 JUMP_ABSOLUTE           13
            >>   56 POP_BLOCK
            >>   57 LOAD_CONST               0 (None)
                 60 RETURN_VALUE
    >>> def Generator_expr(x, y):
    ...    return ((i, j) for i in xrange(x) for j in xrange(y))
    ...
    >>> dis.dis(Generator_expr.func_code.co_consts[1])
      2           0 SETUP_LOOP              47 (to 50)
                  3 LOAD_FAST                0 (.0)
            >>    6 FOR_ITER                40 (to 49)
                  9 STORE_FAST               1 (i)
                 12 SETUP_LOOP              31 (to 46)
                 15 LOAD_GLOBAL              0 (xrange)
                 18 LOAD_DEREF               0 (y)
                 21 CALL_FUNCTION            1
                 24 GET_ITER
            >>   25 FOR_ITER                17 (to 45)
                 28 STORE_FAST               2 (j)
                 31 LOAD_FAST                1 (i)
                 34 LOAD_FAST                2 (j)
                 37 BUILD_TUPLE              2
                 40 YIELD_VALUE
                 41 POP_TOP
                 42 JUMP_ABSOLUTE           25
            >>   45 POP_BLOCK
            >>   46 JUMP_ABSOLUTE            6
            >>   49 POP_BLOCK
            >>   50 LOAD_CONST               0 (None)
                 53 RETURN_VALUE
    • Aceptado – para la explicación detallada de la diferencia mediante la dis. Gracias!
    • He actualizado para incluir un enlace a una fuente que afirma que LOAD_DEREF es «más lento», por lo que si el rendimiento que de verdad importaba algunos reales de tiempo con timeit sería bueno. Un análisis teórico va tan lejos.
  2. 35

    En este ejemplo, en realidad no. Pero yield puede ser utilizado para construcciones más complejas – por ejemplo puede aceptar los valores de la persona que llama así y modificar el flujo como un resultado. Leer PEP 342 para obtener más detalles (es una técnica interesante que vale la pena conocer).

    De todos modos, el mejor consejo es utilizar lo que está más claro para sus necesidades.

    P. S. Aquí está una corutina ejemplo de Dave Beazley:

    def grep(pattern):
        print "Looking for %s" % pattern
        while True:
            line = (yield)
            if pattern in line:
                print line,
    
    # Example use
    if __name__ == '__main__':
        g = grep("python")
        g.next()
        g.send("Yeah, but no, but yeah, but no")
        g.send("A series of tubes")
        g.send("python generators rock!")
    • +1 por la vinculación de David Beazley. Su presentación en corrutinas es el más alucinante que he leído en mucho tiempo. No es tan útil, tal vez, como su presentación en los generadores, pero increíble, no obstante.
  3. 18

    No hay ninguna diferencia para la clase de simples bucles que usted puede caber en un generador de expresión. Sin embargo el rendimiento que se puede utilizar para crear generadores que hacer mucho más complejo de procesamiento. Aquí está un ejemplo simple para la generación de la secuencia de fibonacci:

    >>> def fibgen():
    ...    a = b = 1
    ...    while True:
    ...        yield a
    ...        a, b = b, a+b
    
    >>> list(itertools.takewhile((lambda x: x<100), fibgen()))
    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
    • +1 que es super cool … no se puede decir que jamás he visto un corto y dulce de la fib aplicación sin recursividad.
    • Engañosamente simple fragmento de código – creo Fibonacci será feliz de verla!!
  4. 9

    En el uso, la nota de la distinción entre un objeto de generador vs un generador de función.

    Un generador de objetos de uso-sólo una vez, en contraste con un generador de función, que puede ser reutilizado cada vez que usted llame de nuevo, porque devuelve un nuevo objeto de generador.

    Generador de expresiones en la práctica suele utilizarse «en bruto», sin envuelve en una función, y que devuelva un objeto de generador.

    E. g.:

    def range_10_gen_func():
        x = 0
        while x < 10:
            yield x
            x = x + 1
    
    print(list(range_10_gen_func()))
    print(list(range_10_gen_func()))
    print(list(range_10_gen_func()))

    que las salidas:

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    Comparar con algo un poco diferente uso:

    range_10_gen = range_10_gen_func()
    print(list(range_10_gen))
    print(list(range_10_gen))
    print(list(range_10_gen))

    que las salidas:

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    []
    []

    Y comparar con un generador de expresión:

    range_10_gen_expr = (x for x in range(10))
    print(list(range_10_gen_expr))
    print(list(range_10_gen_expr))
    print(list(range_10_gen_expr))

    que también salidas:

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    []
    []
  5. 8

    Utilizando yield es bueno si la expresión es más complicado de lo que acaba de bucles anidados. Entre otras cosas, usted puede devolver un especial de primera o de especial valor. Considerar:

    def Generator(x):
      for i in xrange(x):
        yield(i)
      yield(None)
  6. 5

    La hora de pensar en los iteradores, el itertools módulo:

    … estandariza un conjunto básico de rápido, la memoria eficiente de herramientas que son útiles por sí solos o en combinación. Juntos, forman un «iterador álgebra», lo que hace posible construir herramientas especializadas de manera sucinta y eficiente en Python puro.

    Rendimiento, considere la posibilidad itertools.producto(*iterables[, repetir])

    Producto cartesiano de entrada iterables.

    Equivalente a anidada para bucles en un generador de expresión. Por ejemplo, product(A, B) devuelve el mismo como ((x,y) for x in A for y in B).

    >>> import itertools
    >>> def gen(x,y):
    ...     return itertools.product(xrange(x),xrange(y))
    ... 
    >>> [t for t in gen(3,2)]
    [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
    >>> 
  7. 3

    Sí, hay una diferencia.

    Para el generador de expresión (x for var in expr), iter(expr) se llama cuando la expresión es creado.

    Cuando se utiliza def y yield para crear un generador, como en:

    def my_generator():
        for var in expr:
            yield x
    
    g = my_generator()

    iter(expr) aún no está llamado. Se llama sólo cuando se itera sobre g (y no podría ser llamado a todos).

    De tomar este iterador como un ejemplo:

    from __future__ import print_function
    
    
    class CountDown(object):
        def __init__(self, n):
            self.n = n
    
        def __iter__(self):
            print("ITER")
            return self
    
        def __next__(self):
            if self.n == 0:
                raise StopIteration()
            self.n -= 1
            return self.n
    
        next = __next__  # for python2

    Este código:

    g1 = (i ** 2 for i in CountDown(3))  # immediately prints "ITER"
    print("Go!")
    for x in g1:
        print(x)

    mientras:

    def my_generator():
        for i in CountDown(3):
            yield i ** 2
    
    
    g2 = my_generator()
    print("Go!")
    for x in g2:  # "ITER" is only printed here
        print(x)

    Ya que la mayoría de los iteradores no hacer un montón de cosas en __iter__, es fácil pasar por alto este comportamiento. Un ejemplo real sería Django del QuerySet, que captura de datos en __iter__ y data = (f(x) for x in qs) puede tomar un montón de tiempo, mientras que def g(): for x in qs: yield f(x) seguido por data=g() regresaría de inmediato.

    Para obtener más información y la definición formal se refieren a PEP 289 — Generador de Expresiones.

  8. 0

    Hay una diferencia que puede ser importante en algunos contextos en los que no ha sido señalado todavía. El uso de yield impide utilizar return por algo más que la implícitamente elevar StopIteration (y co-rutinas relacionadas con la materia).

    Esto significa que este código está mal formado (y dársela a un intérprete le dará una AttributeError):

    class Tea:
    
        """With a cloud of milk, please"""
    
        def __init__(self, temperature):
            self.temperature = temperature
    
    def mary_poppins_purse(tea_time=False):
        """I would like to make one thing clear: I never explain anything."""
        if tea_time:
            return Tea(355)
        else:
            for item in ['lamp', 'mirror', 'coat rack', 'tape measure', 'ficus']:
                yield item
    
    print(mary_poppins_purse(True).temperature)

    Por otro lado, este código funciona como un encanto:

    class Tea:
    
        """With a cloud of milk, please"""
    
        def __init__(self, temperature):
            self.temperature = temperature
    
    def mary_poppins_purse(tea_time=False):
        """I would like to make one thing clear: I never explain anything."""
        if tea_time:
            return Tea(355)
        else:
            return (item for item in ['lamp', 'mirror', 'coat rack',
                                      'tape measure', 'ficus'])
    
    print(mary_poppins_purse(True).temperature)

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea