4
lst = iter([1, 2, 3])
print([*lst])  # >>> [1, 2, 3]
print([*lst])  # >>> []

Is this expected behaviour for unpacking? I would've assumed the original data wouldn't be modified on unpacking and a copy is simply made?

EDIT:

If so, what is the reasoning behind it?

  • Why would you assume a copy is made? Would that be desirable behavior? – roganjosh Aug 24 '18 at 18:02
  • 2
    Here is an answer explaing it: https://stackoverflow.com/a/25336738/8472976 It is not a duplicate, because the question are quite different. – MegaIng Aug 24 '18 at 18:03

3 Answers3

2

Yes, this is expected behavior for unpacking an iterator:

>>> lst = [1, 2, 3]
>>> iter(lst)
<list_iterator at 0x7fffe8b84ef0>

The iterator can only be iterated once and then it is exhausted.

>>> i = iter(lst)
>>> next(i)
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-11-a883b34d6d8a> in <module>()
----> 1 next(i)

StopIteration: 

The iterator protocol specifies that an exhausted iterator must continue raising StopIteration exceptions on subsequent calls of its __next__ method. Therefore, iterating it once again is valid (not an error), but the iterator should yield no new items:

>>> list(i)
[]

Nothing prevents you to define an iterator which disobeys this rule, but such iterators are deemed "broken".

The list iterable could be unpacked multiple times, however.

>>> lst = [1, 2, 3]
>>> print(*lst)
1 2 3
>>> print(*lst)
1 2 3

And you could create as many independent iterators, from the same source list, as you want:

>>> i1 = iter(lst)
>>> i2 = iter(lst)
>>> next(i2)
1
>>> next(i2)
2
>>> next(i1)  # note: it's another 1
1
wim
  • 338,267
  • 99
  • 616
  • 750
2

From the glossary entry for iterators (my emphasis):

A container object (such as a list) produces a fresh new iterator each time you pass it to the iter() function or use it in a for loop. Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.

sacuL
  • 49,704
  • 8
  • 81
  • 106
1

You are confusing the terminology of "iterator" and "iterable".

An iterator (typically) can't be iterated over again. An iterable on the other hand (like a list) can:

lst = [1, 2, 3]
print([*lst])  # >>> [1, 2, 3]
print([*lst])  # >>> [1, 2, 3]
L3viathan
  • 26,748
  • 2
  • 58
  • 81