working with what used print generators funcion for explicacion are python yield generator

what - working with generators in python



Python: expresión del generador vs. rendimiento (8)

Al pensar en iteradores, el módulo itertools :

... estandariza un conjunto básico de herramientas rápidas y eficientes en memoria que son útiles por sí mismas o en combinación. Juntos, forman un "álgebra iterativa" que permite construir herramientas especializadas de manera sucinta y eficiente en Python puro.

Para el rendimiento, considere itertools.product(*iterables[, repeat])

Producto cartesiano de los iterables de entrada.

Equivalente a los for-loops anidados en una expresión de generador. Por ejemplo, el product(A, B) devuelve lo mismo que ((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)] >>>

En Python, ¿hay alguna diferencia entre crear un objeto de generador a través de una expresión de generador versus usar la declaración de rendimiento ?

Usando el rendimiento :

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

Usando la expresión del generador :

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

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

¿Alguna ventaja de uno u otro? ¿Pensamientos?

¡Gracias a todos! ¡Hay mucha información excelente y más referencias en estas respuestas!


En el uso, tenga en cuenta una distinción entre un objeto generador vs una función del generador.

Un objeto generador se usa solo una vez, en contraste con una función del generador, que se puede reutilizar cada vez que se vuelve a llamar, porque devuelve un objeto generador nuevo.

Las expresiones de generador se utilizan en la práctica como "en bruto", sin envolverlas en una función, y devuelven un objeto de generador.

P.ej:

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()))

qué 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]

Compare con un uso ligeramente diferente:

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

qué salidas:

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

Y compare con una expresión de generador:

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 produce:

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


En este ejemplo, no realmente. Pero el yield puede usarse para construcciones más complejas; por ejemplo, también puede aceptar valores de la persona que llama y modificar el flujo como resultado. Lea PEP 342 para más detalles (es una técnica interesante que vale la pena conocer).

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

PD Aquí hay un ejemplo simple de Coroutine 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!")


Existe una diferencia que podría ser importante en algunos contextos que aún no se ha señalado. El uso de yield impide utilizar el return para otra cosa que implícitamente alzar StopIteration (y cosas relacionadas con corutinas) .

Esto significa que este código está mal formado (y alimentarlo a un intérprete le dará un 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)


No hay diferencia para el tipo de bucles simples que puede caber en una expresión de generador. Sin embargo, el rendimiento puede usarse para crear generadores que realizan un procesamiento mucho más complejo. Aquí hay un ejemplo simple para generar la secuencia de fibonacci:

>>> def fibgen(): ... a = b = 1 ... while 1: ... 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]


Sí, hay una diferencia.

Para la expresión del generador (x for var in expr) , se iter(expr) cuando se crea la expresión.

Al usar 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 se llama. Se invocará solo cuando se itere en g (y podría no llamarse en absoluto).

Tomando 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)

Como la mayoría de los iteradores no hacen muchas cosas en __iter__ , es fácil pasar por alto este comportamiento. Un ejemplo del mundo real sería QuerySet de Django, que buscar datos en __iter__ y data = (f(x) for x in qs) podría llevar mucho tiempo, mientras que def g(): for x in qs: yield f(x) seguido de data=g() regresaría inmediatamente.

Para obtener más información y la definición formal, consulte PEP 289 - Expresiones del generador .


Solo hay pequeñas diferencias en los dos. Puede usar el módulo dis para examinar este tipo de cosas por usted mismo.

Editar: Mi primera versión descompila la expresión generadora en módulo-alcance en el aviso interactivo. Eso es ligeramente diferente de la versión del OP con el que se usa dentro de una función. Modifiqué esto para que coincida con el caso real en la pregunta.

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

También podría valer la pena señalar que la expresión del generador normalmente se usaría en línea en el código, en lugar de envolverlo con la función de esa manera. Eso eliminaría un poco la sobrecarga de configuración y mantendría la expresión del generador ligeramente más rápida para valores de bucle más pequeños, incluso si LOAD_FAST diera una ventaja a la versión de "rendimiento".

En ninguno de los casos, la diferencia de rendimiento sería suficiente para justificar la decisión entre uno u otro. La legibilidad cuenta mucho más, por lo tanto, use la que sea más legible para la situación en cuestión.

>>> 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


Usar el yield es bueno si la expresión es más complicada que solo los bucles anidados. Entre otras cosas, puede devolver un primer valor especial o un último valor especial. Considerar:

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