[Python-ideas] Function to return first(or last) true value from list

Peter Otten __peter__ at web.de
Fri Feb 21 10:25:55 CET 2014


Oscar Benjamin wrote:

> On 20 February 2014 22:38, אלעזר <elazarg at gmail.com> wrote:
>>
>> 2014-02-21 0:00 GMT+02:00 Steven D'Aprano <steve at pearwood.info>:
>>>
>>> On Thu, Feb 20, 2014 at 04:14:17PM +0000, Oscar Benjamin wrote:
>>> >
>>> > The thing is just that bare next is not something that's widely
>>> > recognised as being dangerous. I've seen examples of this kind of bug
>>> > in samples from many Python aficionados (including at least one from
>>> > you Terry).
>>>
>>> Why is it dangerous, and what kind of bug?
>>>
>>> If you're talking about the fact that next(it) can raise StopIteration,
>>> I think you are exaggerating the danger. Firstly, quite often you don't
>>> mind if it raises StopIteration, since that's what you would have done
>>> anyway. Secondly, I don't see why raising StopIteration is so much more
>>> dangerous than (say) IndexError or KeyError.
>>>
>> I had this bug just the other day. I did not plan for the empty case,
>> since it was obvious that the empty case is a bug, so I relied on the
>> exception being raised in this case. But I did not get the exception
>> since it was caught in a completely unrelated for loop. It took me a
>> while to figure out what's going on, and it would've taken even more for
>> someone else, not familiar with my assumption or with the whole
>> StopIteration thing (which I believe is the common case). An IndexError
>> or a KeyError would have been great in such a case.
> 
> Exactly. The bug I had manifested in a StopIteration that was raised
> in a semi-deterministic (dependent on slowly varying data) fashion
> after ~1 hour of processing. Had it resulted in an IndexError I would
> have seen a traceback and could have fixed the bug within about 5
> minutes.
> 
> But StopIteration doesn't necessarily bubble up in the same way as
> other exceptions because it can be caught and silently supressed by a
> for loop (or any consumer of iterators). So instead of a traceback I
> had truncated output data. It took some time to discover and verify
> that the output data was truncated. Then it took some time rerunning
> the script under pdb which was no help since it couldn't latch into
> the suppressed exception. I assumed that it must be an exception but
> there were no try/except clauses anywhere in the code. Eventually I
> found it by peppering the code with:
> 
> try:
>     ...
> except Exception as e:
>     import pdb; pdb.set_trace()
> 
> It took most of a day for me to track that down instead of 5 minutes
> precisely because StopIteration is not like other exceptions.
> Admittedly I'd spot a similar bug much quicker now simply because I'm
> aware of the possibility.
> 
> A simplified version of the bug is shown below:
> 
> def itermerge(datasources):
>     for source in datasources:
>         iterator = iter(source)
>         first = next(iterator)
>         for item in iterator:
>             yield first * item
> 
> data = [
>     [1, 1, 2, 3],
>     [1, 4, 5, 6],
>     [],  # Who put that there?
>     [1, 7, 8, 9],
> ]
> 
> for item in itermerge(data):
>     print(item)
> 
> If you run the above then you get:
> 
> $ python tmp.py
> 1
> 2
> 3
> 4
> 5
> 6
> 
> So the data is silently truncated at the empty iterable.
> 
>> It is *very* similar to the "and or" story.

I think the difference is that once you've learned the lesson you stop using 
`and...or` while you change your usage pattern for next() to minimal scopes

def process_source(source):
    it = iter(source)
    first = next(it)
    for item in it:
        yield first * item

def itermerge(sources):
    for source in sources:
        yield from process_source(source)




More information about the Python-ideas mailing list