(original) (raw)

\[Steve Dower\]
In that case, please provide more examples of how it should work when
the assignment expression appears to define a variable in a scope that
is not on the call stack.

Sorry, I'm not clear what you're asking about. Python's scopes are determined statically, at compile-time - they have nothing directly to do with what's on the call stack at runtime. This PEP doesn't change anything about that, either.


Whether intentional or not, there will be changes to how and when names
are resolved.

Assignment expressions don't currently exist, so it's not possible to change how their targets' names get resolved ;-) If the resolution of any \_other\_ names got changed, that would be a bug, not an unintended consequence.

The specification should provide enough information to
determine the preferred behaviour, so we can tell the difference between
intention changes and implementation bugs.

For example, what should be returned from this function?

Thanks! This clearly requires examples to flesh it out.

\>>> A = 0
\>>> def f(x):
... if x:
... \[A := i for i in \[1\]\]
... return A

It depends on how it's called. \`A\` is local to \`f\` in any case (your global \`A\` is wholly irrelevant here).. f(x) would raise UnboundLocalError if \`x\` is not truthy, and return 1 if \`x\` is truthy.
As far as I can tell, the closest current equivalent will not compile:

\>>> A = 0
\>>> def f(x):
... if x:
... def g():
... nonlocal A
... A = 1
... g()
... return A
...
File "", line 4
SyntaxError: no binding for nonlocal 'A' found

That's because it's an incorrect translation: the "if the name is otherwise unknown in the containing block, establish it as local in the containing block" requires code in a "by hand" translation to force that to be the case. "Otherwise unknown" means "is not declared global or nonlocal in the block, and is not already known to be local to the block".

Because \`A\` isn't assigned to in the body of \`f\` outside the listcomp, CPython \_today\_ has no idea \`A\` is intended to be local to \`f\`, so a by-hand translation requires adding silly cruft to force \`A\` to be recognized as local to \`f\`. Perhaps the easiest "covers all cases" way:

def f(x):
if 0: # NEW CODE HERE
A = None # AND HERE
if x:
# etc

That doesn't generate any code at all in CPython (the optimizer throws away the entire "if 0:" block).. But compile-time analysis \_does\_ see the "A = None" binding before the code is thrown away, and that's enough to establish that \`A\` is local to \`f\`. Then it compiles fine today, and:

>>> f(0)
Traceback (most recent call last):
....
return A
UnboundLocalError: local variable 'A' referenced before assignment
>>> f(1)
1


Is this the equivalent behaviour you want?

No, \`A\` is local to \`f\` - no compile-time error is appropriate here.
Or do you want an
UnboundLocalError when calling f(0)?

Yup, because \`A\` is local to \`f\` but not bound at the time it's referenced.
Or do you want the global A to be returned?

If and only if there was a \`global A\` declaration in \`f\`.
How should we approach decision making about these cases as we
implement this? The PEP does not provide enough information for me to
choose the right behaviour here, and I argue that it should.

Well, as before, the PEP pretty clearly (to me) already says that \`A\` is local to \`f\`. Everything else about the semantics follows from that. But also as in an earlier reply, I think the PEP could help by providing some worked-out workalike-function examples. The "if 0:" trick above isn't deep, but it is too "clever" to be obvious. Then again, it's not required to \_specify\_ the semantics, only to illustrate one possible way of \_implementing\_ the semantics.