2012-06-26 11 views
10

Estaba escribiendo una respuesta al this question cuando me di cuenta de que mi implementación simple no producía resultados correctos. Mientras que la caza del insecto, me di cuenta de lo siguiente:¿Por qué zip() suelta los valores de mi generador?

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

Por alguna razón, next() método se llama una vez additioinal gen 's. Para ilustrar esto, he utilizado la siguiente:

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

Respuesta

17

Esto sucede porque zip evalúa iteradores from left to right, lo que significa que, después de tres pasos, que llama next() en gen y sólo entonces iter(range(3)) (o algo así) y encuentros a StopIteration. Para evitar esto, utilice el más corto (finita) iterable como el más a la izquierda argumento:

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

Your self-answer es exactamente correcto, y presenta una muy buena solución - si uno de los argumentos a zip es siempre más corto que el otro. Sin embargo, en situaciones en las que no se sabe cuál será más corto, es posible que encuentre islice útil. islice también proporciona una solución fácil si desea que el primer elemento en sus tuplas sea de su generador. En su caso, usted puede hacer esto:

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

Su respuesta es probablemente mejor en este caso - es sin duda más simple - pero pensé que me gustaría añadir esto como un suplemento.

Cuestiones relacionadas