From: Benjamin Kaplan on
On Wed, Dec 30, 2009 at 7:01 PM, Steven D'Aprano
<steve(a)remove-this-cybersource.com.au> wrote:
>
> I don't see why. What's wrong with it? Unless you embed it in a call to
> list, or similar, it will explicitly raise StopIteration as expected.
>
>
>> I used to have that a lot in cases where not finding at least one valid
>> foo is an actual fatal error.
>
> What's wrong with the obvious solution?
>
> if not any(foo for foo in foos if foo.bar):
>    raise ValueError('need at least one valid foo')

That would require 2 iterations through foos- once in the test, once
for the assignment if successful. If foos takes a long time to iterate
through, it might be faster to put a try-except around the original
statement, catch the StopIteration, and raise a ValueError in its
place. Which I agree is much better practice than letting the
StopIteration signal the fatal error.
From: Peter Otten on
Tom Machinski wrote:

> It would be nice if there was a builtin for "get the first element in
> a genexp, or raise an exception (which isn't StopIteration)", sort of
> like:
>
> from itertools import islice
>
> def first_or_raise(genexp):
> L = list(islice(genexp, 1))
> if not L:
> raise RuntimeError('no elements found')
> return L[0]

Somewhat related in 2.6 there's the next() built-in which accepts a default
value. You can provide a sentinel and test for that instead of using
try...except:

>>> from random import randrange
>>> from functools import partial
>>> def g():
.... return iter(partial(randrange, 3), 2)
....
>>> next(g(), "empty")
1
>>> next(g(), "empty")
1
>>> next(g(), "empty")
'empty'
>>> next(g(), "empty")
'empty'
>>> next(g(), "empty")
'empty'
>>> next(g(), "empty")
0

Peter
From: Steven D'Aprano on
On Wed, 30 Dec 2009 23:20:06 -0500, Benjamin Kaplan wrote:

>>> I used to have that a lot in cases where not finding at least one
>>> valid foo is an actual fatal error.
>>
>> What's wrong with the obvious solution?
>>
>> if not any(foo for foo in foos if foo.bar):
>>    raise ValueError('need at least one valid foo')
>
> That would require 2 iterations through foos- once in the test, once for
> the assignment if successful.

Remember though that any is a lazy test: it returns as soon as it gets a
result. In the case of an empty list, it returns immediately with False,
and in the case of a non-empty list, it returns immediately it reaches a
true item. It doesn't matter if there are twenty thousand items, it will
only look at the first so long as it is true.

Which of course answers my own question... what's wrong with using any is
that it fails if the objects are all considered false in a boolean
context, or if they might be. That means it will work for some objects
(e.g. the re module's MatchObject instances which are always true), but
not for arbitrary objects which may be false.



--
Steven
From: Tom Machinski on
On Wed, Dec 30, 2009 at 4:01 PM, Steven D'Aprano
<steve(a)remove-this-cybersource.com.au> wrote:
> On Wed, 30 Dec 2009 15:18:11 -0800, Tom Machinski wrote:
>> Bottom line, I'm going to have to remove this pattern from my code:
>>
>>   foo = (foo for foo in foos if foo.bar).next()
>
> I don't see why. What's wrong with it? Unless you embed it in a call to
> list, or similar, it will explicitly raise StopIteration as expected.

Exactly; this seems innocuous, but if some caller of this code uses it
in a list() constructor, a very subtle and dangerous bug is introduced
- see OP. This is the entire point of this post.

In a large, non-trivial application, you simply cannot afford the
assumption that no caller will ever do that. Even if you have perfect
memory, some of your other developers or library users may not.

As for what's wrong with the "if not any" solution, Benjamin Kaplan's
post hits the nail on its head. This is a bioinformatics application,
so the iterable "foos" tends to be very large, so saving half the
runtime makes a big difference.

-- Tom
From: Tom Machinski on
On Thu, Dec 31, 2009 at 1:54 AM, Peter Otten <__peter__(a)web.de> wrote:
> Somewhat related in 2.6 there's the next() built-in which accepts a default
> value. You can provide a sentinel and test for that instead of using
> try...except:

Thanks. This can be useful in some of the simpler cases. As you surely
realize, to be perfectly safe, especially when the iterable can
contain any value (including your sentinel), we must use an
out-of-band return value, hence an exception is the only truly safe
solution.

-- Tom