[Python-Dev] Informal educator feedback on PEP 572 (was Re: 2018 Python Language Summit coverage, last part)

Steven D'Aprano steve at pearwood.info
Sun Jun 24 14:06:38 EDT 2018


On Sun, Jun 24, 2018 at 04:33:38PM +1000, Nick Coghlan wrote:
[...]
> > Making the intentional choice to use an assignment expression is not
> > really "implicit" in any meaningful sense.
> 
> No, it's actually implicit: there's an extra "global NAME" or
> "nonlocal NAME" in the equivalent code for a comprehension that isn't
> there in the as-written source code, and doesn't get emitted for a
> regular assignment expression or for the iteration variable in a
> comprehension - it only shows up due to the defined interaction
> between comprehensions and assignment expressions.

You seem to be talking about an implementation which could change in the 
future. I'm talking semantics of the proposed language feature. As a 
programmer writing Python code, I have no visibility into the 
implementation. The implementation could change ten times a day for all 
I care, so long as the semantics remain the same.

I want the desired semantics to drive the implementation, not the 
other way around.

You seem to want the implementation to drive the semantics, by 
eliminating the proposed feature because it doesn't match your 
deep understanding of the implementation as a nested function.

I want this feature because its useful, and without it the use-cases 
for assignment expressions are significantly reduced.

As far as "implicit", for the sake of the discussion, I'll grant you 
that one. Okay, the proposed behaviour will implicitly enable 
comprehensions to export their state.

Now what? Is that a good thing or a bad thing?

If "implicit" (with or without the scare quotes) is such a bad thing to 
be avoided, why are comprehensions implemented using an implicit 
function?


> The problem I have with PEP 572 is that it proposes *breaking that
> otherwise universal pattern* - instead of having assignment
> expressions in comprehensions implicitly declare the name as local in
> the nested comprehension scope, it instead has them:

You talk about "nested comprehension scope", and that's a critical 
point, but I'm going to skip answering that for now. I have a draft 
email responding to another of your posts on that topic, which I hope to 
polish in the next day.


> 1. implicitly declare the name as global or as nonlocal in the
> comprehension (or else raise an exception), depending on the nature of
> the parent scope where the comprehension is used
> 2. in the nonlocal reference case, amend the symbol table analysis to
> act like there was a preceding "if 0:\n for NAME in ():\n pass" in the
> parent scope (so the compiler knows which outer function scope to
> target)

If it is okay for you to amend the list comprehension to behave as if it 
were wrapped in an implicit nested function, why shouldn't it be okay to 
behave as if assignments inside the comprehension included an implicit 
nonlocal declaration?


> The rationale being given for why that is OK is:
> 
> 1. "Everyone" thinks comprehensions are just a for loop (even though
> that hasn't been true for the better part of a decade, and was never
> true for generator expressions)

Obviously "everyone" is an exaggeration, but, yes, I stand by that -- 
most people don't even give comprehension scope a thought until they 
get bitten by it.

Either because (Python 2) they don't realise the loop variable is local 
to their current scope:

http://www.librador.com/2014/07/10/Variable-scope-in-list-comprehension-vs-generator-expression/

or (Python 3) they get bitten by the change:

https://old.reddit.com/r/Python/comments/425qmb/strange_python_27_34_difference/

(As is so often the case, whatever behaviour we choose, we're going to 
surprise somebody.)

It is hardly surprising that people don't think too hard about scoping 
of comprehensions. Without a way to perform assignments inside 
comprehensions, aside from the loop variables themselves, there's 
nothing going on inside a comprehension where it makes a visible 
difference whether it is a local scope or a sublocal scope.

*IF* assignment expressions are introduced, that is going to change. We 
have some choices:

1. Keep assignment expressions encapsulated in their implicit function, 
and be prepared for people to be annoyed because (with no way to declare 
them global or non-local inside an expression), they can't use them to 
get data in and out of the comprehension.


2. Allow assignment expressions to be exported out of the comprehension, 
and be prepared for people to be annoyed because they clobbered a local.

(But for the reasons Tim Peters has already set out, I doubt this will 
happen often.)


3. Allow some sort of extra comprehension syntax to allow 
global/nonlocal declarations inside comprehensions.

    x = 1
    [nonlocal x := x+i for i in sequence]

(Hmmm... I thought I would hate that more than I actually do.)


4. Have some sort of cunning plan whereby if the variable in question 
exists in the local scope, it is implicitly local inside the 
comprehension:

    x = 1
    [x := i+1 for i in (1, 2)]
    assert x == 3

but if it doesn't, then the variable is implicitly sublocal inside the 
comprehension:

    del x
    [x := i+1 for i in (1, 2)]
    x  # raises NameError



Remember, the driving use-case which started this (ever-so-long) 
discussion was the ability to push data into a comprehension and then 
update it on each iteration, something like this:

    x = initial_value()
    results = [x := transform(x, i) for i in sequence]

Please, Nick, take your implementor's hat off, forget everything you 
know about the implementation of comprehensions and their implicit 
nested function, and tell me that doesn't look like it should work.




-- 
Steve


More information about the Python-Dev mailing list