[Python-Dev] PEP 572: Assignment Expressions

Steven D'Aprano steve at pearwood.info
Sat Apr 21 13:41:26 EDT 2018


On Sun, Apr 22, 2018 at 12:45:36AM +1000, Chris Angelico wrote:

> > The reason I want items to "leak" into the surrounding scope is mostly
> > so that the initial value for it can be set with a simple assignment
> > outside the comprehension:
> >
> >     items = (1, 2, 3)
> >     [ ... items := items*2 ... ]
> >
> > and the least magical way to do that is to just make items an ordinary
> > local variable.
> 
> You can't have your cake and eat it too. Iteration variables and names
> bound by assignment expressions are both set inside the comprehension.

You say that as if it were a law of physics, rather than an 
implementation choice.


> Either they both are local, or they both leak - or else we have a
> weird rule like "the outermost iterable is magical and special".

We *already* have the rule that the outermost iterable is special, 
except it isn't a rule precisely, since (as far as I know) it isn't 
documented anywhere, nor was it ever planned as a feature. It's just an 
accidental(?) consequence of the implementation choices made.


py> spam = [(1,2), (3, 4)]
py> [spam for x in spam for spam in x]  # first loop is magic
[1, 2, 3, 4]


but:


py> spam = [(1,2), (3, 4)]
py> [spam for _ in [1] for x in spam for spam in x]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
UnboundLocalError: local variable 'spam' referenced before assignment


However the second example worked fine in Python 2. Changing the 
implementation of comprehensions to be like generator expressions and 
avoid leaking the loop variables seems to have had the (accidental?) 
side-effect of making the first loop magical.


[...]
> PEP 572 corrects this by making it behave the way that you, and many
> other people, expect. Current behaviour is surprising because the
> outermost iterable is special and magical.

This shouldn't be PEP 572's job.

It's unfair on you to be shouldered with sheparding through what is
effectively two complex PEPs ("assignment-expressions" plus "fix 
comprehension scoping") in one. Especially if you had no idea at the 
start that this is what is involved.

And it's even more unfair on those who may not care two hoots about 
assignment-expressions, but would be really interested in comprehension 
scoping if only they knew we were talking about that.

And it makes this PEP harder work for readers who don't care about 
comprehension scoping.

I think that we should be able to make any of the following choices (or 
no choice at all) regarding comprehensions:

* no change: semantics remains underspecified, defined by 
  "whatever the implementation does";
* lock in the current behaviour as a language promise;
* change the behaviour and make it a language promise;

regardless of what is decided about PEP 572.


[...]
> > Are you sure about this example?
> 
> Yes, I'm sure. You may notice that I didn't iterate over the genexps
> in my example.

No, I didn't notice that was your intent. I thought it was just 
short-hand.


> The first one will bomb out, even without iteration;

And that's yet another oddity, one I didn't think of. It's downright 
bizarre that these two genexps behave differently:

spam = [1, 2]
eggs = 12
(x+y for x in spam for y in eggs)  # okay
(x+y for y in eggs for x in spam)  # TypeError

and I'd be surprised to learn that this behavour was planned in advance. 
("Early binding and ahead-of-time type-testing for the first loop, late 
binding and just-in-time type-testing for the second loop. All in 
favour?")

But it is what it is, and who knows, maybe we decide we *want* this 
behaviour, bizarre as it is. It isn't clear to me that:


1. it's necessarily "broken" and needs fixing;

2. if if does need fixing, it needs to be fixed *right* now;

3. that acceptance or rejection of PEP 572 needs to hinge on the 
   decision about comprehensions;

4. and especially that a change to comprehensions ought to be
   smuggled in via an unrelated PEP.

(I know that 4 is not your intention, but that's the way it may appear.)


-- 
Steve


More information about the Python-Dev mailing list