What Is Meaning Of [iter(list)]*2 In Python?
Solution 1:
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)] * 2list(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.
Solution 2:
iter(lst)
turns a list into an iterator. Iterators let you step lazily through an iterable by calling next()
until the iterator runs out of items.
[iter(lst)]
puts the iterator into a single-element list.
[iter(lst)] * 2
makes 2 copies of the iterator in the list, giving
it = iter(lst)
[it, it]
Both list elements are aliases of the same underlying iterator object, so whenever next()
is called on either of the iterators as zip
exhausts them, successive elements are yielded.
*[...]
unpacks the list of the two copies of the same iterator into the arguments for zip
. This creates a zip object that lets you iterate through tuples of elements from each of its arguments.
list(...)
iterates through the zip object and copies the elements into a list. Since both zipped iterators point to the same underlying iterator, we get the sequential elements seen in your output.
Without using the iterator alias, you'd get
>>>list(zip(iter(lst), iter(lst)))
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8)]
A similar way to write list(zip(*[iter(lst)] * 2))
is list(zip(lst[::2], lst[1::2]))
, which seems a bit less magical (if much less performant).
The explanation for
>>>list(zip(*[iter(lst)] * 3))
[(1, 2, 3), (4, 5, 6)]
omitting elements is that the first time the zip object tries to yield a None
result on any of the argument iterables, it stops and does not generate a tuple. You can use itertools.zip_longest
to match your expected behavior, more or less:
>>> list(zip_longest(*[iter(lst)] * 3))
[(1, 2, 3), (4, 5, 6), (7, 8, None)]
See the canonical answer List of lists changes reflected across sublists unexpectedly if the [...] * 2
aliasing behavior is surprising.
Post a Comment for "What Is Meaning Of [iter(list)]*2 In Python?"