Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #80390 > unrolled thread

Different behaviour in list comps and generator expressions

Started bySteven D'Aprano <steve+comp.lang.python@pearwood.info>
First post2014-11-08 12:50 +1100
Last post2014-11-17 18:20 -0800
Articles 7 — 5 participants

Back to article view | Back to comp.lang.python


Contents

  Different behaviour in list comps and generator expressions Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-11-08 12:50 +1100
    Re: Different behaviour in list comps and generator expressions Roy Smith <roy@panix.com> - 2014-11-07 23:39 -0500
    Re: Different behaviour in list comps and generator expressions Wolfgang Maier <wolfgang.maier@biologie.uni-freiburg.de> - 2014-11-08 22:31 +0100
    Re: Different behaviour in list comps and generator expressions Wolfgang Maier <wolfgang.maier@biologie.uni-freiburg.de> - 2014-11-08 23:42 +0100
    Re: Different behaviour in list comps and generator expressions Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-11-18 10:30 +1100
      Re: Different behaviour in list comps and generator expressions Dan Stromberg <drsalists@gmail.com> - 2014-11-17 15:38 -0800
      Re: Different behaviour in list comps and generator expressions Ethan Furman <ethan@stoneleaf.us> - 2014-11-17 18:20 -0800

#80390 — Different behaviour in list comps and generator expressions

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-11-08 12:50 +1100
SubjectDifferent behaviour in list comps and generator expressions
Message-ID<545d76fe$0$12980$c3e8da3$5496439d@news.astraweb.com>
The following list comprehension and generator expression are almost, but
not quite, the same:

[expr for x in iterable]

list(expr for x in iterable)


The difference is in the handling of StopIteration raised inside the expr.
Generator expressions consume them and halt, while comprehensions allow
them to leak out. A simple example:

iterable = [iter([])]
list(next(x) for x in iterable)
=> returns []

But:

[next(x) for x in iterable]
=> raises StopIteration


Has anyone come across this difference in the wild? Was it a problem? Do you
rely on that difference, or is it a nuisance? Has it caused difficulty in
debugging code?

If you had to keep one behaviour, which would you keep?



-- 
Steven

[toc] | [next] | [standalone]


#80391

FromRoy Smith <roy@panix.com>
Date2014-11-07 23:39 -0500
Message-ID<roy-E5EC13.23390507112014@news.panix.com>
In reply to#80390
In article <545d76fe$0$12980$c3e8da3$5496439d@news.astraweb.com>,
 Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:

> The following list comprehension and generator expression are almost, but
> not quite, the same:
> 
> [expr for x in iterable]
> 
> list(expr for x in iterable)
> 
> 
> The difference is in the handling of StopIteration raised inside the expr.
> Generator expressions consume them and halt, while comprehensions allow
> them to leak out. A simple example:
> 
> iterable = [iter([])]
> list(next(x) for x in iterable)
> => returns []
> 
> But:
> 
> [next(x) for x in iterable]
> => raises StopIteration
> 
> 
> Has anyone come across this difference in the wild? Was it a problem? Do you
> rely on that difference, or is it a nuisance? Has it caused difficulty in
> debugging code?
> 
> If you had to keep one behaviour, which would you keep?

Wow, that's really esoteric.  I can't imagine this happening in 
real-life code (but I'm sure somebody will come up with an example :-))

My inclination is that a list comprehension should stop if StopIteration 
is raised by the comprehension body.  I can't come up with a good 
argument to support that, other than it seems like the right thing to do.

[toc] | [prev] | [next] | [standalone]


#80406

FromWolfgang Maier <wolfgang.maier@biologie.uni-freiburg.de>
Date2014-11-08 22:31 +0100
Message-ID<mailman.15588.1415482299.18130.python-list@python.org>
In reply to#80390
On 08.11.2014 02:50, Steven D'Aprano wrote:
> The following list comprehension and generator expression are almost, but
> not quite, the same:
>
> [expr for x in iterable]
>
> list(expr for x in iterable)
>
>
> The difference is in the handling of StopIteration raised inside the expr.
> Generator expressions consume them and halt, while comprehensions allow
> them to leak out.

This is not the right description of what's happening. It is not the 
generator expression that consumes it, but the list constructor and, of 
course, list can't tell at which level in the inside code StopIteration 
got raised.
So, yes this had me confused some times, too, but it is really not 
surprising.

> A simple example:
>
> iterable = [iter([])]
> list(next(x) for x in iterable)
> => returns []
>
> But:
>
> [next(x) for x in iterable]
> => raises StopIteration
>

Yes, but the equivalent to list(next(x) for x in iterable) is:

l = []
for item in (next(x) for x in iterable):
     l.append(item)

=> returns [] because the for consumes the StopIteration

this, on the other hand raises StopIteration

l = []
for item in [next(x) for x in iterable]:
     l.append(item)

because the error gets raised already when for causes iter() to be 
called on the list comprehension.

>
> Has anyone come across this difference in the wild? Was it a problem? Do you
> rely on that difference, or is it a nuisance? Has it caused difficulty in
> debugging code?
>
> If you had to keep one behaviour, which would you keep?
>

The point is: there is no difference in the behavior of comprehensions 
and generator expressions, it is just how you access them:
for anything that follows the iterator protocol, a comprehension (since 
it is evaluated immediately) raises during the iter() phase, while a 
generator expression (which gets evaluated lazily) raises during the 
next() phase where it gets swallowed.

So in your example you would have to use the silly

list([next(x) for x in iterable])

if you want the error to get raised.

I agree this is all rather non-intuitive, but how would you change it ?

[toc] | [prev] | [next] | [standalone]


#80408

FromWolfgang Maier <wolfgang.maier@biologie.uni-freiburg.de>
Date2014-11-08 23:42 +0100
Message-ID<mailman.15589.1415486565.18130.python-list@python.org>
In reply to#80390
On 08.11.2014 22:31, Wolfgang Maier wrote:
> On 08.11.2014 02:50, Steven D'Aprano wrote:
>> The following list comprehension and generator expression are almost, but
>> not quite, the same:
>>
>> [expr for x in iterable]
>>
>> list(expr for x in iterable)
>>
>>
>> The difference is in the handling of StopIteration raised inside the
>> expr.
>> Generator expressions consume them and halt, while comprehensions allow
>> them to leak out.
>
> This is not the right description of what's happening. It is not the
> generator expression that consumes it, but the list constructor and, of
> course, list can't tell at which level in the inside code StopIteration
> got raised.
> So, yes this had me confused some times, too, but it is really not
> surprising.
>
>> A simple example:
>>
>> iterable = [iter([])]
>> list(next(x) for x in iterable)
>> => returns []
>>
>> But:
>>
>> [next(x) for x in iterable]
>> => raises StopIteration
>>
>
> Yes, but the equivalent to list(next(x) for x in iterable) is:
>
> l = []
> for item in (next(x) for x in iterable):
>      l.append(item)
>
> => returns [] because the for consumes the StopIteration
>
> this, on the other hand raises StopIteration
>
> l = []
> for item in [next(x) for x in iterable]:
>      l.append(item)
>
> because the error gets raised already when for causes iter() to be
> called on the list comprehension.
>
>>
>> Has anyone come across this difference in the wild? Was it a problem?
>> Do you
>> rely on that difference, or is it a nuisance? Has it caused difficulty in
>> debugging code?
>>
>> If you had to keep one behaviour, which would you keep?
>>
>
> The point is: there is no difference in the behavior of comprehensions
> and generator expressions, it is just how you access them:
> for anything that follows the iterator protocol, a comprehension (since
> it is evaluated immediately) raises during the iter() phase, while a
> generator expression (which gets evaluated lazily) raises during the
> next() phase where it gets swallowed.
>
> So in your example you would have to use the silly
>
> list([next(x) for x in iterable])
>
> if you want the error to get raised.
>
> I agree this is all rather non-intuitive, but how would you change it ?
>
>

Ah, I came across the related thread on python-ideas only now and from 
this I can see that, as I expected, you know everything I've written 
above already.

In light of the discussion on the other list:
I did find it annoying occasionally that raising StopIteration inside a 
generator expression conveys a different behavior than elsewhere. It did 
take me quite a while to understand why that is so, but after that it 
did not cause me much of a headache anymore.

I would say that Guido's suggestion of transforming StopIteration raised 
inside a generator into some other error to eliminate ambiguity would 
really help in some situations, but then I'm not sure whether that's 
worth breaking existing code.

Best,
Wolfgang

[toc] | [prev] | [next] | [standalone]


#80987

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-11-18 10:30 +1100
Message-ID<546a8524$0$12990$c3e8da3$5496439d@news.astraweb.com>
In reply to#80390
Steven D'Aprano wrote:

> The following list comprehension and generator expression are almost, but
> not quite, the same:
> 
> [expr for x in iterable]
> 
> list(expr for x in iterable)
> 
> 
> The difference is in the handling of StopIteration raised inside the expr.
[...]


Thanks to Roy and Wolfgang for their comments.

If anyone is interested, Guido will soon be ruling on a PEP which will
change the behaviour of generators and generator expressions, and he has
asked for feedback. In particular, examples of code which will be broken by
this change.

Read the PEP here:

https://www.python.org/dev/peps/pep-0479


If you post comments here, I will see that they get forwarded on.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#80988

FromDan Stromberg <drsalists@gmail.com>
Date2014-11-17 15:38 -0800
Message-ID<mailman.15964.1416267547.18130.python-list@python.org>
In reply to#80987
On Mon, Nov 17, 2014 at 3:30 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
>> The following list comprehension and generator expression are almost, but
>> not quite, the same:
>>
>> [expr for x in iterable]
>>
>> list(expr for x in iterable)
>>
>>
>> The difference is in the handling of StopIteration raised inside the expr.
> [...]
>
>
> Thanks to Roy and Wolfgang for their comments.
>
> If anyone is interested, Guido will soon be ruling on a PEP which will
> change the behaviour of generators and generator expressions, and he has
> asked for feedback. In particular, examples of code which will be broken by
> this change.
>
> Read the PEP here:
>
> https://www.python.org/dev/peps/pep-0479
>
>
> If you post comments here, I will see that they get forwarded on.

I'm inclined to say that list comprehensions and generator expressions
should be different.  I don't really think they should be identical,
one being eager and one being lazy.  Why let the implementation detail
of one impact the other?

[toc] | [prev] | [next] | [standalone]


#80992

FromEthan Furman <ethan@stoneleaf.us>
Date2014-11-17 18:20 -0800
Message-ID<mailman.15966.1416277211.18130.python-list@python.org>
In reply to#80987

[Multipart message — attachments visible in raw view] — view raw

On 11/17/2014 03:38 PM, Dan Stromberg wrote:
> 
> I'm inclined to say that list comprehensions and generator expressions
> should be different.  I don't really think they should be identical,
> one being eager and one being lazy.  Why let the implementation detail
> of one impact the other?

It's not the eagerness vs. laziness that's being changed, but rather what happens in a generator when something inside
the generator raises StopIteration (as opposed to the generator itself simply return'ing and thereby causing a
StopIteration to be generated).

--
~Ethan~

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web