The following I had with Python 3.8.1 (on macOS Mojave, 10.14.6, as
well as Python 3.7 (or some older) on some other platforms). I'm new
to computing and don't know how to request an improvement of a
language, but I think I've found a strange behaviour of the built-in
function map
.
As the code next(iter(()))
raises StopIteration
, I expected to
get StopIteration
from the following code:
tuple(map(next, [iter(())]))
To my surprise, this silently returned the tuple ()
!
So it appears the unpacking of the map object stopped when
StopIteration
came from next
hitting the "empty" iterator
returned by iter(())
. However, I don't think the exception was
handled right, as StopIteration
was not raised before the "empty"
iterator was picked from the list (to be hit by next
).
- Did I understand the behaviour correctly?
- Is this behaviour somehow intended?
- Will this be changed in a near future? Or how can I get it?
Edit: The behaviour is similar if I unpack the map object in different ways, such as by list
, for for-loop, unpacking within a list, unpacking for function arguments, by set
, dict
. So I believe it's not tuple
but map
that's wrong.
Edit: Actually, in Python 2 (2.7.10), the "same" code raises
StopIteration
. I think this is the desirable result (except that map
in this case does not return an iterator).
This isn't a map
bug. It's an ugly consequence of Python's decision to rely on exceptions for control flow: actual errors look like normal control flow.
When map
calls next
on iter(())
, next
raises StopIteration
. This StopIteration
propagates out of map.__next__
and into the tuple
call. This StopIteration
looks like the StopIteration
that map.__next__
would normally raise to signal the end of the map, so tuple
thinks that the map is simply out of elements.
This leads to weirder consequences than what you saw. For example, a map
iterator doesn't mark itself exhausted when the mapped function raises an exception, so you can keep iterating over it even afterward:
m = map(next, [iter([]), iter([1])])
print(tuple(m))
print(tuple(m))
Output:
()
(1,)
(The CPython map
implementation doesn't actually have a way to mark itself exhausted - it relies on the underlying iterator(s) for that.)
This kind of StopIteration problem was annoying enough that they actually changed generator StopIteration handling to mitigate it. StopIteration used to propagate normally out of a generator, but now, if a StopIteration would propagate out of a generator, it gets replaced with a RuntimeError so it doesn't look like the generator ended normally. This only affects generators, though, not other iterators like map
.