2012-07-19 34 views
9

¿Alguien podría explicar el comportamiento de un bucle anidado utilizando generadores? Aquí hay un ejemplo.El bucle anidado de Python con generadores no funciona (en algunos casos)?

a = (x for x in range(3)) 
b = (x for x in range(2)) 
for i in a: 
    for j in b: 
     print (i,j) 

El bucle externo no se evalúa después de la primera iteración por algún motivo. El resultado es,

(0, 0) 
(0, 1) 

Por otro lado, si los generadores se insertan directamente en los bucles, que hace lo que espero.

for i in (x for x in range(3)): 
    for j in (x for x in range(2)): 
     print (i,j) 

dando todos 3x2 pares.

(0, 0) 
(0, 1) 
(1, 0) 
(1, 1) 
(2, 0) 
(2, 1) 

Respuesta

22

es porque el generador de b se agota durante la primera iteración de la exterior para el bucle. Las iteraciones posteriores tendrán, de hecho, un bucle interno vacío (como for x in()), por lo que lo que está adentro nunca se ejecutará. Esto da la falsa impresión de que es el bucle externo el que falla, de alguna manera.

Su segundo ejemplo funciona porque allí el generador interno se crea de nuevo para cada bucle externo. Para solucionar el primer ejemplo, usted tiene que hacer lo mismo:

a = (x for x in range(3)) 
for i in a: 
    b = (x for x in range(2)) 
    for j in b: 
     print (i,j) 
+0

Aha! No noté el agotamiento del generador. Muchas gracias. – phantomile

8

@lazyr ha contestado esta manera brillante, pero me gustaría señalar de remisión que al utilizar generadores anidados vale la pena saber acerca itertools.product ...

for i, j in itertools.product(range(3), range(2)): 
    print (i, j) 

o (si usted tiene un montón de Vals):

for vals in itertools.product(range(45), range(12), range(3)): 
    print (sum(vals)) 

es (en mi humilde opinión) legible y evita la sangría excesiva.

0

itertools.product es la mejor opción para este ejemplo. Pero es posible que desee más opciones durante las iteraciones. Aquí es una manera de seguir recibiendo el producto en su ejemplo sin utilizar el método del producto:

a = (range(2) for x in range(3)) 
for i in a: 
    for j in i: 
     print (i,j) 

Además, utilizo itertoolz.concat de la biblioteca de ayuda funcional para racionalizar pytoolz/aplanar casos como este. concat es igual itertools.chain, sino que tiene un solo argumento que da iteradores que consiguen desenredado:

from pytoolz import itertoolz 
a = (((x,y) for y in range(2)) for x in range(3)) 
for i,j in itertoolz.concat(a): 
    print (i,j) 

Por lo tanto, lo anterior se ve menos legible que el método del producto, pero permite transformaciones de grano más fino/filtrado en cada bucle nivel. Y, por supuesto, no tiene anidados bucles for durante la iteración final de la lógica, que puede ser agradable.

Además, si se utiliza pytoolz que probablemente debería utilizar cytoolz, es la misma biblioteca compilado en C

Cuestiones relacionadas