Guido and I were looking at an old bug this week. It’s about an inconsistency in how variables are resolved in class namespaces. There are several possible solutions, including closing the bug as “working as intended.” I drafted a PEP to describe the problem and propose three non-trivial solutions. I’d be interested to hear which of the approaches in the PEP seem reasonable to people.
Does not it conflict with my PEP 837?
I didn’t see your PEP 837! I thought the process was to take the next available number. How can we fix it?
837-839 are all open PRs. The next available is 840, let’s switch this one to that: python/peps#5043.
For next time, see hugovk.dev/next-pep-number/ or:
❯ uvx pepotron next
Next available PEP: 840
Loooking a bit more about the various examples, I think this is the one that’s wrong (and has been since 3.4):
x = 1
def f(x):
class Test:
x = x
return Test
print(f(2).x)
This prints 1 because for a variable name that the static analysis sees is written in the class, the classic rule “try locals then globals” is used regardless of whether there’s a definition for the name in a containing function scope. But when the name is not assigned locally and there’s a containing function scope that defines the name, the rule “locals, then outer” is used. OTOH when there’s no definition in a containing function scope, the classic rule is also used.
My view is that for the first case (written locally), if there’s a definition in an outer function scope, the “locals, then outer” rule should also be used.
What’s the rationale for classes and functions differing in this respect?
>>> x = 0
>>> def f(x):
... def g():
... x = x
... return g
...
>>> f(1)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in g
UnboundLocalError: local variable 'x' referenced before assignment
>>>
I feel uncertainty about the two kinds of name resolution working differently. Why should the variable x in “x = x” mean different things on the LHS and RHS?
The best case I can make is backwards compatibility. You could refer to two different variables with “x = x” for so long that it’s too late to change it.
Jeremy
Name lookup in a class scope is just different. It’s normally dynamically scoped: local first, then fallback (global or containing function). This is different than name lookup in a function — that’s always statically defined (the decision to load a global is statically made based on scope analysis).