Published: 27 January 2021, 19:00
Today Guido van Rossum posted a Python riddle:
x = 0
y = 0
def f():
x = 1
y = 1
class C:
print(x, y) # What does this print?
x = 2
f()
The answer is 0 1
.
The first tip is if you replace the class with a function, it will fail:
x = 0
y = 0
def f():
x = 1
y = 1
def f2():
print(x, y)
x = 2
f2()
f()
# UnboundLocalError: local variable 'x' referenced before assignment
Why so? The answer can be found in the documentation (see Execution model):
If a variable is used in a code block but not defined there, it is a free variable.
So, x
is a free variable but y
isn’t, this is why behavior for them is different. And when you try to use a free variable, the code fails at runtime because you haven’t defined it yet in the current scope but will define it later.
Let’s disassemble the snippet above:
import dis
dis.dis("""[insert here the previous snippet]""")
It outputs a lot of different things, this is the part we’re interested in:
8 0 LOAD_GLOBAL 0 (print)
2 LOAD_FAST 0 (x)
4 LOAD_DEREF 0 (y)
6 CALL_FUNCTION 2
8 POP_TOP
Indeed, x
and y
have different instructions, and they’re different at bytecode-compilation time. Now, what’s different for a class scope?
import dis
dis.dis("""[insert here the first code snippet]""")
This is the same dis part for the class:
8 8 LOAD_NAME 3 (print)
10 LOAD_NAME 4 (x)
12 LOAD_CLASSDEREF 0 (y)
14 CALL_FUNCTION 2
16 POP_TOP
So, the class scope behaves differently. x
and y
loaded with LOAD_FAST
and LOAD_DEREF
for a function and with LOAD_NAME
and LOAD_CLASSDEREF
for a class.
The same documentation page answers how this behavior is different:
Class definition blocks and arguments to exec() and eval() are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace.
In other words, if a variable in the class definition is unbound, it is looked up in the global
namespace skipping enclosing nonlocal
scope.