2012-07-19 10 views
9

Qualcuno potrebbe spiegare il comportamento di un ciclo annidato usando i generatori? Ecco un esempio.Il loop annidato di Python con generatori non funziona (in alcuni casi)?

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) 

Il ciclo esterno non viene valutata dopo la prima iterazione per qualche ragione. Il risultato è,

(0, 0) 
(0, 1) 

D'altra parte, se i generatori vengono inseriti direttamente nelle anse, che fa quello che mi aspetto.

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

dare tutte le coppie 3x2.

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

risposta

22

è perché il generatore b si esaurisce durante la prima iterazione del ciclo for esterno. Le iterazioni successive avranno in effetti un ciclo interno vuoto (come for x in()), quindi ciò che è dentro non verrà mai eseguito. Questo dà la falsa impressione che sia il ciclo esterno che fallisce, in qualche modo.

Il secondo esempio funziona perché il generatore interno viene creato di nuovo per ciascun ciclo esterno. Per risolvere il tuo primo esempio, quello che dovete fare lo stesso:

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! Non ho notato l'esaurimento del generatore. Grazie mille. – phantomile

8

@lazyr ha risposto a questa brillantemente, ma vorrei sottolineare di rinvio risulta che quando si utilizzano generatori nidificate è da sapere su itertools.product ...

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

o (se si dispone di un sacco di vals):

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

e '(IMHO) leggibile ed evita il rientro eccessivo.

0

itertools.product è la soluzione migliore per questo esempio. Ma potresti volere più opzioni durante le iterazioni. Ecco un modo per ottenere ancora il prodotto nel tuo esempio senza utilizzare il metodo del prodotto:

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

Inoltre, io uso itertoolz.concat dalla libreria di supporto funzionale pytoolz per razionalizzare/appiattire casi come questo. concat è proprio come itertools.chain ma invece prende un singolo argomento che produce iteratori che vengono svelato:

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) 

Quindi, quanto sopra appare meno leggibile rispetto al metodo prodotto, ma consente una grana fine trasformazioni/filtraggio ad ogni ciclo livello. E naturalmente, non hai nested for-loop durante la logica di iterazione finale che può essere carina.

Inoltre, se si utilizza pytoolz probabilmente si dovrebbe utilizzare cytoolz, è la stessa libreria compilata in C.

Problemi correlati