Jacques Gaudin Jacques Gaudin - 7 months ago 27
Python Question

ValueError raised with map(next, iterables)

I am writing a loop over 4 list-iterators like this :

myIter0 = iter(['foo','foo','bar','foo','spam','spam'])
myIter1 = iter(['foo','bar','spam','foo','spam','bar'])
myIter2 = iter(['foo','foo','bar','foo','spam','spam'])
myIter3 = iter(['foo','bar','spam','foo','spam','bar'])

try:
a, b, c, d = map(next, (myIter0, myIter1, myIter2, myIter3))
while True:
#some operations that can raise ValueError
if a+b+c+d == 'foobarfoobar':
a, b, c, d = map(next, (myIter0, myIter1, myIter2, myIter3))
elif a+b == 'foobar':
c, d = map(next,(myIter2, myIter3))
else:
a, b = map(next,(myIter0, myIter1))
except StopIteration:
pass


But when I run this code, I get a
ValueError: not enough values to unpack (expected 2, got 0)
raised before the expected
StopIteration
.

I do not want to change the exception
StopIteration
to
ValueError
in the
except
statement because some operations could result in a
ValueError
that I do not want to ignore.

How is it that
next
doesn't raise its exception before the assignement ?

EDIT0: In Python2.7,
StopIteration
is raised first.

EDIT1:

In the light of Martijn Pieters answer below, I created a function:

def get_next_of(*args):
return [next(arg) for arg in args]


that works like the Python2.7
map
and raises
StopIteration
.

Note the function returning a tuple doesn't work, in accordance with the explanation given in the accepted answer.

Also, another way around using list comprehension
[next(it) for it in list_iter]

Answer

The StopIteration is raised, but the tuple assignment swallows this as it sees the StopIteration as a signal from the map() iterator that it is done producing values:

>>> i0, i1 = iter(['foo']), iter([])
>>> m = map(next, (i0, i1))
>>> next(m)
'foo'
>>> next(m)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> i0, i1 = iter(['foo']), iter([])
>>> m = map(next, (i0, i1))
>>> a, b = m
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 2, got 1)

This is normal behaviour; Python built-ins that expect an iterator as input always use StopIteration as the 'iteration is done' signal, and tuple unpacking must iterate here.

Either convert the map() output to a list first and test the length, use next() on each iterable separately and not use map(), or catch ValueError locally.

Testing the length would have to re-raise StopIteration:

values = list(map(next, (myIter0, myIter1, myIter2, myIter3)))
if len(values) < 4:
    raise StopIteration
a, b, c, d = values

Note that here list() has swallowed the StopIteration exception.

Catching the ValueError just for the map() operation:

try:    
    a, b, c, d = map(next, (myIter0, myIter1, myIter2, myIter3))
except ValueError:
    raise StopIteration

Not using map() at all:

a, b, c, d = next(myIter0), next(myIter1), next(myIter2), next(myIter3)