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