[Python-Dev] PEP 572: Assignment Expressions

Steven D'Aprano steve at pearwood.info
Tue Apr 17 21:20:48 EDT 2018


On Wed, Apr 18, 2018 at 10:13:58AM +1000, Chris Angelico wrote:

[regarding comprehensions]

> The changes here are only to edge and corner cases, other than as they
> specifically relate to assignment expressions. The current behaviour
> is intended to "do the right thing" according to people's
> expectations, and it largely does so; those cases are not changing.
> For list comprehensions at global or function scope, the ONLY case
> that can change (to my knowledge) is where you reuse a variable name:
> 
> [t for t in t.__parameters__ if t not in tvars]
> 
> This works in 3.7 but will fail easily and noisily (UnboundLocalError)
> with PEP 572.

That's a major semantic change, and the code you show is no better or 
worse than:

    t = ...
    result = []
    for t in t.parameters:
        if t not in tvars:
            result.append(t)


which is allowed. I think you need a better justification for breaking 
it than merely the opinion:

> IMO this is a poor way to write a loop,

Reusing names is permitted in Python. If you're going to break code, 
that surely needs a future import or deprecation period. As you say 
yourself, you've already found one example in the standard library that 
will break.


> and the fact
> that it "happened to work" is on par with code that depended on dict
> iteration order in Python 3.2 and earlier.

I don't think that's justified. As far as I can tell, the fact that it 
works is not a mere accident of implementation but a consequence of the 
promised semantics of comprehensions and Python's scoping rules.

If that's not the case, I think you need to justify exactly why it isn't 
guaranteed.


> Yes, the implementation is
> well defined, but since you can achieve exactly the same thing by
> picking a different variable name, it's better to be clear.

Ah, but the aim of the PEP is not to prohibit ugly or unclear code.


> Note that the second of the open questions would actually return this
> to current behaviour, by importing the name 't' into the local scope.

Indeed. Maybe this needs to stop being an open question and become a 
settled question.

 
> The biggest semantic change is to the way names are looked up at class
> scope. Currently, the behaviour is somewhat bizarre unless you think
> in terms of unrolling a loop *as a function*; there is no way to
> reference names from the current scope, and you will instead ignore
> the surrounding class and "reach out" into the next scope outwards
> (probably global scope).
> 
> Out of all the code in the stdlib, the *only* one that needed changing
> was in Lib/typing.py, where the above comprehension was found. (Not
> counting a couple of unit tests whose specific job is to verify this
> behaviour.)

If there are tests which intentionally verify this behaviour, that 
really hurts your position that the behaviour is an accident of 
implementation. It sounds like the behaviour is intended and required.


> The potential for breakage is extremely low. Non-zero, but
> far lower than the cost of introducing a new keyword, for instance,
> which is done without deprecation cycles.

Which new keywords are you thinking of? The most recent new keywords I 
can think of were "True/False", "as" and "with".

- True, False became keywords in 3.x during the "breaking code 
  is allowed" 2 -> 3 transition;

- "as" became a keyword in 2.6 following a deprecation period in 2.5:

py> as = 1
<stdin>:1: Warning: 'as' will become a reserved keyword in Python 2.6

- and "with" needed a __future__ import.

Have I missed any?


-- 
Steve


More information about the Python-Dev mailing list