[Python-ideas] A comprehension scope issue in PEP 572

Tim Peters tim.peters at gmail.com
Tue May 8 13:57:33 EDT 2018

These all match my expectations.  Some glosses:

> So the way I envision it is that *in the absence of a nonlocal or global
> declaration in the containing scope*, := inside a comprehension or genexpr
> causes the compiler to assign to a local in the containing scope, which is
> elevated to a cell (if it isn't already). If there is an explicit nonlocal
> or global declaration in the containing scope, that is honored.

If the genexp/listcomp is at module level, then "assign to a local in
the containing scope" still makes sense ("locals" and "globals" mean
the same thing at module level), but "elevated to a cell" doesn't then
- it's just a plain global.

In absolutely all cases, what I expect is that

    NAME := EXPR

in a genexp/listcomp do the binding _as if_

    NAME = object_EXPR_evaluates_to

were executed in the immediately containing scope.  Describing the
goal instead of part of the implementation may be easier to grasp ;-)

> Examples:
>   # Simplest case, neither nonlocal nor global declaration
>   def foo():
>       [p := q for q in range(10)]  # Creates foo-local variable p
>       print(p)  # Prints 9
>   # There's a nonlocal declaration
>   def bar():
>       p = 42  # Needed to determine its scope
>       def inner():
>           nonlocal p
>           [p := q for q in range(10)]  # Assigns to p in bar's scope
>       inner()
>       print(p)  # Prints 9
>   # There's a global declaration
>   def baz():
>       global p
>       [p := q for q in range(10)]
>   baz()
>   print(p)  # Prints 9
> All these would work the same way if you wrote list(p := q for q in
> range(10)) instead of the comprehension.

100% agreed.  Add at module scope:

    [p := q for q in range(10)]
    print(p) # Prints 9

But uou're on your own for class scope, because I never did anything
fancy enough at class scope to need to learn how it works ;-)

> We should probably define what happens when you write [p := p for p in
> range(10)]. I propose that this overwrites the loop control variable rather
> than creating a second p in the containing scope -- either way it's probably
> a typo anyway.

A compile-time error would be fine by me too.  Creating two meanings
for `p` is nuts - pick one in case of conflict.  I suggested before
that the first person with a real use case for this silliness should
get the meaning their use case needs, but nobody bit, so "it's local
then" is fine.

> := outside a comprehension/genexpr is treated just like any other assignment
> (other than in-place assignment), i.e. it creates a local unless a nonlocal
> or global declaration exists.

Also agreed.  People have total control over scopes in explicitly
given functions now, and if the compiler magically made anything
nonlocal they would have no way to stop it.  Well, I suppose we could
add a "non_nonlocal" declaration, but I'd rather not ;-)

More information about the Python-ideas mailing list