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

Terry Reedy tjreedy at udel.edu
Sun Sep 11 12:47:08 EDT 2011


On 9/11/2011 12:01 AM, Ian Kelly wrote:
> On Sat, Sep 10, 2011 at 1:36 PM, Terry Reedy<tjreedy at udel.edu>  wrote:
>> The statement containing the explicit next(items) call can optionally be
>> wrapped to explicitly handle the case of an empty iterable in whatever
>> manner is desired.
>>
>> try:
>>     <set up with next(items)>
>> except StopIteration:
>>     raise ValueError("iterable cannot be empty")
>
> The only time that's optional

This is an opinion, or rather, a programming philosophy based on 
technical facts, rather than a fact itself. You do raise an important issue.

> is when you're writing an iterator and
> the try-except would end up looking like this:
>
> try:
>      # do stuff with next(items)
> except StopIteration:
>      raise StopIteration
>
> And even then, it's probably a good idea to clearly document that
> you're allowing a StopIteration from one iterator to propagate up as a
> StopIteration for another.

In the yield-pairs example,

def pairs(iterable):
     it = iter(iterable)
     for i in it:
         yield i, next(it)

ignoring StopIteration from the get-even explicit next means ignoring an 
odd item. If pairs() were in a general purpose library, it should have a 
doc string that specifies that, and a test case with an odd number of 
items. I would consider a comment in the code itself to be optional, 
depending on the intended or expected human audience and the context of 
presentation. In this context, the explanation is in the text that 
surrounds the code.

> Apart from that case, whenever you call next() you should always be
> prepared to catch a StopIteration.

To me, it depends on the contract or specification of the function. If 
the behavior for an empty input iterator is not specified, then there is 
no basis for writing the body of an except clause.

While in the past few months I have written examples of all of the three 
explicit-next use cases I gave, I was prompted to write them up now by 
Tigerstyle's 'Doctest failing' thread. The specification by example 
(perhaps given by an instructor) did not include an empty title that 
would lead to an empty list of title words. Certainly, the doc for

def fix_title(title):
     small_words = ('into', 'the', 'a', 'of', 'at', 'in', 'for', 'on')
     twords = iter(title.strip().lower().split())
     new_title = [next(twords)]
     for word in twords:
         if word not in small_words:
             word = word.title()
         new_title.append(word)
     return(' '.join(new_title))

should start "Given a title with at least one word, ...".

The Agile Programming Test-Driven-Development maxim, 'Write the minimum 
code needed to pass the test' says that the extra lines needed to catch 
and process StopIteration should *not* be written until there is a test 
case leading to such.

> Letting a StopIteration propagate
> up the stack to parts unknown is bad juju because it's a flow control
> exception, not an error-signaling exception.  If it happens to
> propagate up to another for loop, then it will break out of the for
> loop, and the exception will simply be swallowed.

What you are saying is a) that the following code

for title in ['amazinG', 'a helL of a fiGHT', '', 'igNordEd']:
     print(fix_title(title))

will print 'Amazing', 'A Hell of a Fight', and stop; b) that this is the 
worst choice of how to handle the empty title; and c) that in real world 
world programming, *someone* should decide whether fix_title('') returns 
'' or raises ValueError.

A counter-argument could be 1. that when a function's contract is 
violated, invoking unspecified behavior, anything is allowed; or 2. that 
titles are checked for actual content before the case fixup is called, 
and the time and mental energy required to define and test behavior for 
impossible input is wasted and better spent on something more useful.

-- 
Terry Jan Reedy




More information about the Python-list mailing list