# Python etc / class scope

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)
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)

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.
In other words, if a variable in the class definition is unbound, it is looked up in the global namespace skipping enclosing nonlocal scope.