Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does zip() drop the values of my generator?

Tags:

python

I was writing an answer to this question when noticed that my simple implementation didn't produce correct results. While hunting down the bug, I noticed the following:

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

For whatever reason, gen's next() method is called one additioinal time. To illustrate this, I used the following:

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)]
like image 216
Lev Levitsky Avatar asked Jun 26 '12 15:06

Lev Levitsky


People also ask

Does zip return a generator Python?

The zip() function is not a generator function, it just returns an iterators.

What does the function zip () do?

Definition and Usage. The zip() function returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc.

What does zip (*) do in Python?

Python zip() The zip() function takes iterables (can be zero or more), aggregates them in a tuple, and returns it.

How many arguments can zip take Python?

It can take zero or more arguments and it returns us an iterator to the tuple.


2 Answers

This happens because zip evaluates iterators from left to right, meaning that, after three steps, it calls next() on gen and only then on iter(range(3)) (or something like that) and encounters a StopIteration. To get around this, use the shorter (finite) iterable as the left-most argument:

In [8]: zip(range(3), gen)
0
1
2
Out[8]: [(0, 0), (1, 1), (2, 2)]
like image 177
Lev Levitsky Avatar answered Sep 28 '22 01:09

Lev Levitsky


Your self-answer is exactly right, and presents a very good solution -- if one of the arguments to zip is always shorter than the other. However, in situations where you don't know which will be shorter, you might find islice useful. islice also provides an easy workaround if you want the first item in your tuples to be from your generator. In your case, you could do this:

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

Your answer is probably better in this case -- it's certainly simpler -- but I thought I'd add this as a supplement.

like image 44
senderle Avatar answered Sep 28 '22 01:09

senderle