[Python-ideas] sentinel_exception argument to `iter`

Terry Reedy tjreedy at udel.edu
Fri Feb 7 03:36:19 CET 2014


On 2/6/2014 7:10 PM, Ram Rachum wrote:

> `iter` has a very cool `sentinel` argument. I suggest an additional
> argument `sentinel_exception`; when it's supplied, instead of waiting
> for a sentinel value, we wait for a sentinel exception to be raised, and
> then the iteration is finished.
>
> This'll be useful to construct things like this:
>
>      my_iterator = iter(my_deque.popleft, IndexError)
>
> What do you think?

I think this would be a great idea if simplified to reuse the current 
parameter. It can work in Python because exceptions are objects like 
anything else and can be passed as arguments. No new parameter is 
needed. We only need to add 'or raises' to the two-parameter iter definition
"In the second form, the callable is called until it returns the sentinel."
to get
"In the second form, the callable is called until it returns or raises 
the sentinel."

With this version of the proposal, your pop example would work as written.

The two-parameter form return a 'callable_iterator' object. Its __next__ 
method in Python might look like (untested)

def __next__(self):
   x = self.func()
   if x == self.sentinel:
     raise StopIteration
   else:
     return x

The only change needed for the added behavior is to wrap the function 
call and change an exception matching the sentinel to StopIteration.

def __next__(self):
   try:
     x = self.func()
   except Exception as exc:
     if isinstance(exc, self.sentinel):
       raise StopIteration from None
   if x == self.sentinel:
     raise StopIteration
   else:
     return x

I do something similar to this in my custom function tester and it is 
really handy to be able to pass in an expected exception as the expected 
'output'. For example, my 'test table' for an int divide function might 
contain these input and expected output pairs:
(2,0), ZeroDevisionError
(2,1), 2
(2,2), 1.

I consider the threat to backward compatibility, because of the added 
test for exceptions, theoretical rather than actual. It is very rare to 
write a function that returns an exception, rarer still to write one 
that can also raise an instance of the same exception class. Also, using 
an exception-returning function in two-parameter iter is tricky because 
exceptions inherit the default equality comparison of only equal by 
identity.

 >>> ValueError('high') == ValueError('high')
False
 >>> e=ValueError('high')
 >>> e == e
True

The following tested example of code that would be broken by the 
proposal could well be the first ever written. Warning: it is so twisted 
and awful that it might to read it.

---
from random import random

e=ValueError('high value')

def crazy():
   x = random()
   if x > .999:
     return e
   elif x < .001:
     raise ValueError('low value')
   else:
     return x

try:
   for x in iter(crazy, e):
     pass
   else:
     print(e.args[0])
except ValueError as exc:
   print(exc.args[0])
#
prints 'high value' or 'low value' in less than a second

-- 
Terry Jan Reedy



More information about the Python-ideas mailing list