2012-06-26 8 views
10

Stavo scrivendo una risposta a this question quando ho notato che la mia semplice implementazione non ha prodotto risultati corretti. Mentre la caccia al bug, ho notato quanto segue:Perché zip() rilascia i valori del mio generatore?

In [1]: import itertools 
In [2]: gen = itertools.cycle((0,1,2)) 

In [3]: zip(gen, range(3)) 
Out[3]: [(0, 0), (1, 1), (2, 2)] 

In [4]: zip(gen, range(3)) 
Out[4]: [(1, 0), (2, 1), (0, 2)] 

Per qualsiasi motivo, gen s' next() metodo viene chiamato una sola volta additioinal. Per illustrare questo, ho usato il seguente:

class loudCycle(itertools.cycle): 
    def next(self): 
     n = super(loudCycle, self).next() 
     print n 
     return n 

In [6]: gen = loudCycle((0,1,2)) 
In [7]: zip(gen, range(3)) 
0 
1 
2 
0 
Out[7]: [(0, 0), (1, 1), (2, 2)] 

risposta

17

Ciò accade perché zip viene valutata iteratori from left to right, il che significa che, dopo tre passi, chiama next() su gen e solo allora iter(range(3)) (o qualcosa di simile) e incontri a StopIteration. Per aggirare il problema, utilizzare il più breve (finito) iterabile come il più a sinistra argomento:

In [8]: zip(range(3), gen) 
0 
1 
2 
Out[8]: [(0, 0), (1, 1), (2, 2)] 
7

Your self-answer è esattamente a destra, e presenta una soluzione molto buona - se uno degli argomenti a zip è sempre più corto dell'altro. Tuttavia, in situazioni in cui non sai quale sarà più breve, potresti trovare utile islice. islice fornisce anche una soluzione semplice se si desidera che il primo elemento delle tuple provenga dal generatore. Nel tuo caso, si potrebbe fare questo:

>>> import itertools 
>>> gen = itertools.cycle(('a', 'b', 'c')) 
>>> seq = range(3) 
>>> zip(itertools.islice(gen, len(seq)), seq) 
[('a', 0), ('b', 1), ('c', 2)] 
>>> zip(itertools.islice(gen, len(seq)), seq) 
[('a', 0), ('b', 1), ('c', 2)] 

La tua risposta è probabilmente meglio in questo caso - è certamente più semplice - ma ho pensato che vorrei aggiungere questo come un integratore.

Problemi correlati