Search code examples
pythoniteratorzip

what is meaning of [iter(list)]*2 in python?


I have found below code in web, result is tuple of two elements in list, how to understand [iter(list)]*2?

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

[(1, 2), (3, 4), (5, 6), (7, 8)]

------------
[iter(lst)]*2
[<list_iterator at 0x1aff33917f0>, <list_iterator at 0x1aff33917f0>]

I check [iter(lst)]*2, same iterator above, so meaning iter repeat double, so, if i check num from 2 to 3, result should be [(1, 2, 3), (4, 5, 6),(7,8,NaN)] but delete 7,8

lst = [1,2,3,4,5,6,7,8]
b=zip(*[iter(lst)]*3)
list(b)
--------------
[(1, 2, 3), (4, 5, 6)]

Solution

  • Quite a tricky construct to explain. I'll give it a shot:

    with [iter(lst)] you create a list with with one item. The item is an iterator over a list.

    whenever python tries to get an element from this iterator, then the next element of lst is returned until no more element is available.

    Just try following:

    i = iter(lst)
    next(i)
    next(i)
    

    the output should look like:

    >>> lst = [1,2,3,4,5,6,7,8]  
    >>> i = iter(lst)
    >>> next(i)
    1
    >>> next(i)
    2
    >>> next(i)
    3
    >>> next(i)
    4
    >>> next(i)
    5
    >>> next(i)
    6
    >>> next(i)
    7
    >>> next(i)
    8
    >>> next(i)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    Now you create a list that contains twice exactly the same iterator. You do this with itlst = [iter(lst)] * 2

    try out following:

    itlst1 = [iter(lst)] * 2
    itlst2 = [iter(lst), iter(lst)]
    print(itlst1)
    print(itlst2)
    

    The result will look something like:

    >>> itlst1 = [iter(lst)] * 2
    >>> itlst2 = [iter(lst), iter(lst)]
    >>> print(itlst1)
    [<list_iterator object at 0x7f9251172b00>, <list_iterator object at 0x7f9251172b00>]
    >>> print(itlst2)
    [<list_iterator object at 0x7f9251172b70>, <list_iterator object at 0x7f9251172ba8>]
    

    What is important to notice is, that itlst1 is a list containing twice the same iterator, whereas itlst2 contains two different iterators.

    to illustrate try to type:

    next(itlst1[0])
    next(itlst1[1])
    next(itlst1[0])
    next(itlst1[1])
    

    and compare it with:

    next(itlst2[0])
    next(itlst2[1])
    next(itlst2[0])
    next(itlst2[1])
    

    The result is:

    >>> next(itlst1[0])
    1
    >>> next(itlst1[1])
    2
    >>> next(itlst1[0])
    3
    >>> next(itlst1[1])
    4
    >>> 
    >>> next(itlst2[0])
    1
    >>> next(itlst2[1])
    1
    >>> next(itlst2[0])
    2
    >>> next(itlst2[1])
    2
    

    Now to the zip() function ( https://docs.python.org/3/library/functions.html#zip ):

    Try following:

    i = iter(lst)
    list(zip(i, i))
    

    zip() with two parameters. Whenver you try to get the next element from zip it will do following:

    • get one value from the iterable that is the first parameter
    • get one value from the iterable that is the second parameter
    • return a tuple with these two values.

    list(zip(xxx)) will do this repeatedly and store the result in a list.

    The result will be:

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

    The next trick being used is the * that is used to use the first element as first parameter to a function call, the second element as second parameter and so forth) What does ** (double star/asterisk) and * (star/asterisk) do for parameters?

    so writing:

    itlst1 = [iter(lst)] * 2
    list(zip(*itlst1))
    

    is in this case identical to

    i = iter(lst)
    itlst1 = [i] * 2
    list(zip(itlst1[0], itlst1[1]))
    

    which is identical to

    list(zip(i, i))
    

    which I explained already.

    Hope this explains most of the above tricks.