Python3: Does the built-in function "map" have a bug?

Takuo Matsuoka :

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).

  1. Did I understand the behaviour correctly?
  2. Is this behaviour somehow intended?
  3. 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).

user2357112 supports Monica :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=360955&siteId=1