Idioms combining 'next(items)' and 'for item in items:'

Terry Reedy tjreedy at udel.edu
Mon Sep 12 22:51:01 CEST 2011


On 9/12/2011 12:55 PM, Ian Kelly wrote:
> On Sun, Sep 11, 2011 at 6:45 PM, Terry Reedy<tjreedy at udel.edu>  wrote:
>> whereas, you are right, it breaks it noisily in the body. So Ian's claim
>> that StopIteration must be caught to avoid silent termination is not true.
>> Thanks for pointing out what I saw but did not cognize the full implication
>> of before. A better exception and an error message with an explaination
>> might still be a good idea, though.
>
> But you can't write the function under the assumption that it will
> only be called from the function body.

Sigh. You are right.

>  The following is a slight
> reorganization of your example that does exhibit the problem:
>
> for title in map(fix_title, ['amazinG', 'a helL of a fiGHT', '', 'igNordEd']):
>      print(title)
>
> Output:
> amazing
> a Hell of a Fight
>
> Note that at first glance, my example would appear to be functionally
> equivalent to yours -- I've merely pulled the fix_title call out of
> the loop body and into the iterator.  But actually they produce
> different results because fix_title misbehaves by not catching the
> StopIteration.

You are right, non-iterators should not raise or pass on StopIteration. 
There are actually several reasons.

1. The manual defined StopIteration as meaning '[I have] no more values 
[to give you]'. This is only meaningful coming from an iterator.

2. Your particular point is that StopIteration is (almost) unique in 
being sometimes, but only sometimes, caught by the interpreter, rather 
than just by user except clauses. AttributeError is another, which has 
occasionally caused its own problems. But we cannot stop raising 
AttributeError while we can always catch StopIteration for explicit 
next() (and should outside of iterators).

3. In the case of grabbing the first item from an iterator, no first 
item is a boundary case for the expected, legal type of input. I believe 
boundary cases should be included in function specifications. While 
there may be a couple of choices as to response, that is much less than 
infinity. For fix_title, the choices are ValueError or ''. Any other 
return would be an error unless explicitly given in the specs. So the 
boundary case should be included in the test suite to exclude any other 
random response.

4. StopIteration is an artifact of the choice of implementation. Pulling 
the first item out before the loop is an alternative to a flag and 
testing within the loop. Such an implementation detail should not leak 
into the user's view of the function as an abstraction.

If fix_title were a generator function whose instances yielded fixed 
title words one at a time, then the bare next() would be correct (as you 
noted). But it is not, and the difference is important, more important 
than having 'minimal clean code'. Thank you for persisting until I saw 
that in this context.

-- 
Terry Jan Reedy




More information about the Python-list mailing list