StopIteration in the if clause of a generator expression

Carl Banks invalidemail at aerojockey.com
Fri Apr 1 11:18:46 EST 2005


Peter Otten wrote:
> To confuse a newbies and old hands alike, Bengt Richter wrote:
>
> > Need something more straightforward, e.g., a wrapped one-liner:
> >
> >  >>> def guess(n=3): print ("You're right!", 'No more tries for
> >  >>> you!!!')[n-1 in
> >  ...    (x for x in xrange(n) for t in [raw_input('Guess my name:
> >  ')=='Ben']
> >  ...    if not t or iter([]).next())]
> >  ...
> >  >>> guess()
>
> To make it a bit clearer, a StopIteration raised in a generator
expression
> silently terminates that generator:
>
> >>> def stop(): raise StopIteration
> ...
> >>> list(i for i in range(10) if i < 5 or stop())
> [0, 1, 2, 3, 4]
>
> In a list comprehension, on the other hand, it is propagated:
>
> >>> [i for i in range(10) if i < 5 or stop()]
> Traceback (most recent call last):
>   File "<stdin>", line 1, in ?
>   File "<stdin>", line 1, in stop
> StopIteration
>
> Is that an intentional difference?


Very interesting.  I'm not sure if the designers even considered this
particular subtlety.  Why it happens is pretty plain.  In the generator
expression case, the generator expression does propogate the
StopIteration, but list() traps it.  List comprehensions are internally
treated as a for-loop (kind of), which doesn't trap StopIteration.
Maybe it should.

The list comprehension [ x for x in y ] is currently treated as
equivalent to the following, with byte-code optimizations:

. _ = []
. for x in y:
.     _.append(x)


Perhaps it ought to be equivalent to:

. _ = []
. try:
.     for x in y:
.         _.append(x)
. except StopIteration:
.     pass


However, I would guess the Python gods wouldn't approve of this use of
StopIteration, and so would make no sacrifices to get it.
Nevertheless, it seems likely to be how a list comprehension would
behave in Python 3.0, so maybe we should do it.


-- 
CARL BANKS




More information about the Python-list mailing list