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

Ethan Furman ethan at stoneleaf.us
Sun Sep 11 15:01:53 EDT 2011


Terry Reedy wrote:
> 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.


Having spent hours tracking down errors because somebody did not address 
  a corner case, I find counter-argument 1 unhelpful.

Counter-argument 2 I can at least partially agree with; however, in this 
case where the uncaught exception can mess up flow control elsewhere I 
do not -- the StopIteration should be caught and changed to something 
appropriate, such as EmptyTitle (assuming blank titles are errors -- 
which they probably should be... "Hello, do you have the book '' for 
sale here?")

~Ethan~



More information about the Python-list mailing list