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