[Tutor] Python Idioms?

Steven D'Aprano steve at pearwood.info
Wed Apr 1 15:01:44 CEST 2015


On Tue, Mar 31, 2015 at 09:50:43PM -0700, Jim Mooney wrote:
> I'm looking at this and can't see how it works, although I understand
> zipping and unpacking. The docs say it's a Python idiom. Does "idiom" mean
> it works in a special way so I can't figure it out from basic principles?
> It looks to me like the iterator in the list gets doubled, so the zip
> should make it [(1,1),(2,2),(3,3),... ], not [(1,2),(3,4),...]
> 
> What am I missing here?

The secret is that list multiplication doesn't make *copies*, it 
replicates multiple references to the same object.

> >>> s = [1,2,3,4,5,6,7,8]
> >>> list(zip(*[iter(s)]*2))
> >>> [(1, 2), (3, 4), (5, 6), (7, 8)]


Let's pull this apart and see how it ticks. It may actually be more 
obvious what is going on if we use more than two references, and do by 
hand what * does for us above.

py> s = [100, 200, 300, 400, 500, 600, 700, 800, 900]
py> it = iter(s)  # One iterator object.
py> next(it), next(it), next(it)  # Call next three times.
(100, 200, 300)
py> args = [it, it, it]  # Like [it]*3
py> for x in args:
...     print(next(x))  # Like next(it).
...
400
500
600


We created *one* iterator. First we called next(it) three times, by 
hand, which yields the first three items in the list s. Then we stick 
the iterator in a new list three times, and loop over that, calling 
next() each time. That is equivalent to next(it) three more times, which 
gives us the next three items.

Finally, we pass them to zip(), as separate arguments:

py> list(zip(*args))
[(700, 800, 900)]


Which gives us the next three items. At that point, we run out of items, 
and zip completes.

Putting it (almost) all together now. Remember that `it` above, the 
iterator, is now exhausted. It has walked all the way through list s, so 
we need a new iterator to make it work again:

py> list(it)  # it is exhausted.
[]
py> it = iter(s)  # So re-create it.
py> list(zip(*[it]*3))
[(100, 200, 300), (400, 500, 600), (700, 800, 900)]


We can avoid the temporary variable:

py> list(zip(*[iter(s)]*3))
[(100, 200, 300), (400, 500, 600), (700, 800, 900)]


-- 
Steve


More information about the Tutor mailing list