Dangerous behavior of list(generator)

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Wed Dec 30 19:01:39 EST 2009


On Wed, 30 Dec 2009 15:18:11 -0800, Tom Machinski wrote:

> Thanks for the comment and discussion guys.
> 
> 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.


> 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')



> But using StopIteration to signal a fatal
> condition becomes a bug when interacting with list() as shown in the
> original post.

You shouldn't use StopIteration to signal fatal conditions, because 
that's not what it is for. It's acceptable to catch it when *directly* 
calling next, but otherwise you should expect that StopIteration will be 
caught and suppressed by just about anything.


> 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)", 

Not everything needs to be a built-in.

def get_first_or_fail(iterable_or_sequence):
    it = iter(iterable_or_sequence)
    try:
        return it.next()  # use next(it) in Python 3
    except StopIteration:
        raise ValueError('empty iterable')

This is perfectly usable as a helper function, or it's short enough to be 
used in-line if you prefer.


-- 
Steven



More information about the Python-list mailing list