[Python-ideas] Map and filter should also convert StopIteration to RuntimeError

Terry Reedy tjreedy at udel.edu
Fri Dec 12 22:14:07 CET 2014

" exception StopIteration
     Raised by built-in function next() and an iterator‘s __next__() 
method to signal that there are no further items produced by the iterator."

I have always taken this to mean that these are the only functions that 
should raise StopIteration, but other have not, and indeed, 
StopIteration in generator functions, which are not __next__ methods, 
has been accepted and passed on by generator.__next__.  PEP 479 reverses 
this acceptance by having generator.__next turn StopIteration raised in 
a user-written generator function body into a RuntimeError.  I propose 
that other builtin iterator.__next__ methods that execute a passed in 
function do the same.

This proposal comes from Oscar Benjamin's comments on the PEP in the 
'Generator are iterators' thread.  In one post, he gave this example.

 >>> def func(x):
...     if x < 0:
...         raise StopIteration
...     return x ** 2
 >>> it = map(func, [1, 2, 3, -1, 2])
 >>> list(it)
[1, 4, 9]
 >>> list(it)  # map continues to yield values...

Function func violates what I think should be the guideline.  When 
passed to map, the result is a silently buggy iterator.  The doc says 
that map will "Return an iterator that applies function to every item of 
iterable, yielding the results.".  If map cannot do that, because func 
raises for some item in iterable, map should also raise, but the 
exception should be something other than StopIteration, which signals 
normal non-buggy completion of its task.

I propose that map.__next__ convert StopIteration raised by func to 
RuntimeError, just as generator.__next__ now does for StopIteration 
raised by executing a generator function frame.  (And same for filter().)

class newmap:
     "Simplified version allowing just one input iterable."
     def __iter__(self):
         return self
     def __init__(self, func, iterable):
         self.func = func

         self.argit = iter(iterable)
     def __next__(self):
         func = self.func
         args = next(self.argit)  # pass on expected StopIteration
         if func is None:
             return args
             try:  # new wrapper
                 return func(args)
             except StopIteration:
                 raise RuntimeError('func raised StopIteration')

Terry Jan Reedy

More information about the Python-ideas mailing list