Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is an uninitialized class variable looked up in the global scope and not the enclosing scope?

this is not a problem I need for a coding project, but a friend of mine sent this to me. The question is: what gets printed out here? and why?

x = 0
y = 0
def f():
    x = 1
    y = 1
    class A:
        print(x,y)
        x = 2
f()

Output:

0 1

my first instinct told me that it should be "1 1", but I was wrong. I fiddled with the code and found out that if I remove the "x = 2" I would have been correct. but why?

also, it worked when I made the variables global. like this:

x = 0
y = 0
def f():
    global x
    global y
    x = 1
    y = 1
    class A:
        print(x,y)
        x = 2
f()

Output:

1 1

I am not looking for other solutions to the problem, just a better understanding of what is going on here.

thanks in advance

like image 294
KrissKloss Avatar asked Dec 23 '22 16:12

KrissKloss


1 Answers

This is documented in the last paragraph of Resolution of names:

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. (emphasis mine)

So, y is looked up, as expected, in the nearest enclosing scope that defines it (the f function, where it gets the value 1).

On the other end, x = 2 causes x to be considered local, and as it isn't already defined in print(x,y), the exception causes it to be looked up in the global namespace (where it gets the value 0), instead of causing a NameError, as would be expected for an unbound local variable in a normal function.


I have long wondered why this special rule existed, and I just found the answer thanks to @orlp's comment under the question.

The code in the question seems to come from this 2014 blog post from KMOD'S BLOG, and was recently tweeted by Guido van Rossum.

And as someone asked about the reason why, Guido answered:

Backwards compatibility with Python 2.1.

So now we know...

like image 148
Thierry Lathuille Avatar answered Feb 16 '23 02:02

Thierry Lathuille