tags:

views:

237

answers:

3

I saw this in the Python documentation for zip(). Apparently, if s is [1,2,3,4,5,6,7,8,9] and n is 3, zip(*[iter(s)]*n) returns [(1,2,3),(4,5,6),(7,8,9)]. How exactly does this work? I'm not sure how the operator precedence works here. What would this look like if it was written with more verbose code?

Thanks.

(This just looks so cool I gotta know.)

Thanks for all the answers. I understand it fully now. The magic (or what I was not understanding) was in the iter(). Because it's the same iter(), when zip calls next() on each of the 3 iterators, it really calls next() on the same iterator, thus forwarding the iterator one at a time and giving a sequential list of groupings.

+8  A: 

iter() is an iterator over a sequence. [x] * n is a list containing n quantity of x. *arg unpacks a sequence into arguments for a function call. Therefore you're passing the same iterator 3 times to zip(), and it pulls an item from the iterator each time.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
Congrats on your 1024'th answer (even if you stole it to me ! :D)
Luper Rouch
+1  A: 

iter(s) returns an iterator for s.

[iter(s)]*n makes a list of n times the same iterator for s.

So, when doing zip(*[iter(s)]*n), it extracts an item from all the three iterators from the list in order. Since all the iterators are the same object, it just groups the list in chunks of n.

sttwister
Not 'n iterators of the same list', but 'n times the same iterator object'. Different iterator objects don't share state, even when they are of the same list.
Thomas Wouters
Thanks, corrected. Indeed that was what I was "thinking", but wrote something else.
sttwister
+3  A: 

The other great answers and comments explain well the roles of argument unpacking and zip().

As Ignacio and ujukatzel say, you pass to zip() three identical iterators and zip() makes 3-tuples of the integers—in order—from each of the three iterators:

1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

And since you ask for a more verbose code sample:

chunk_size = 3
L = [1,2,3,4,5,6,7,8,9]

# iterate over L in steps of 3
for start in range(0,len(L),chunk_size): # xrange() in 2.x; range() in 3.x
    end = start + chunk_size
    print L[start:end] # three-item chunks

Following the values of start and end:

[0:3) #[1,2,3]
[3:6) #[4,5,6]
[6:9) #[7,8,9]

FWIW, you can get the same result with map() with an initial argument of None:

>>> map(None,*[iter(s)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

For more on zip() and map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

Adam Bernier
It's not three copies of the same iterator, it's three times the same iterator object :)
Thomas Wouters
Thanks Thomas. Re-worded.
Adam Bernier