[Python-Dev] PEP 572: Assignment Expressions

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


On Sat, Apr 21, 2018 at 05:46:44PM +1000, Chris Angelico wrote:
> On Sat, Apr 21, 2018 at 5:11 PM, Steven D'Aprano <steve at pearwood.info> wrote:

> > So can you explain specifically what odd function-scope behaviour you
> > are referring to? Give an example please?
> 
> doubled_items = [x for x in (items := get_items()) if x * 2 in items]
> 
> This will leak 'items' into the surrounding scope (but not 'x').

The "not x" part is odd, I agree, but it's a popular feature to have 
comprehensions run in a separate scope, so that's working as designed.

The "leak items" part is the behaviour I desire, so that's not odd, it's 
sensible *wink*

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.


> [x for x in x if x] # This works

The oddity is that this does work, and there's no assignment expression 
in sight.

Given that x is a local variable of the comprehension `for x in ...` it 
ought to raise UnboundLocalError, as the expanded equivalent does:


def demo():
    result = []
    for x in x: # ought to raise UnboundLocalError
        if x:
            result.append(x)
    return result


That the comprehension version runs (rather than raising) is surprising 
but I wouldn't call it a bug. Nor would I say it was a language 
guarantee that we have to emulate in similar expressions.

In the absence of either explicit documentation of this behaviour, or 
Guido or one of the senior core developers explicitly stating that it is 
intentional behaviour that should be considered a language promise, I'd 
call it an accident of implementation.

In which case, the fact that your next example:

> [x for y in x if x := y] # UnboundLocalError

"correctly" raises, as does the expanded version:


def demo():
    result = []
    for y in x:  # ought to raise UnboundLocalError
        x = y    # since x is a local
        if x:
            result.append(x)
    return result


shouldn't be seen as a problem. The code is different, so why should it 
behave the same?


> (x for x in 5) # TypeError
> (x for _ in [1] for x in 5) # Works

Now that last one is more than just odd, it is downright bizarre. Or at 
least it would, if it did work:

py> list((x for _ in [1] for x in 5))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
TypeError: 'int' object is not iterable


Are you sure about this example?

In any case, since this has no assignment expression in it, I don't see 
why it is relevant.



-- 
Steve


More information about the Python-Dev mailing list