1

As you may know, the scope of a variable is statically determined in python (What are the rules for local and global variables in Python?).

For instance :

a = "global" def function(): print(a) a = "local" function() # UnboundLocalError: local variable 'a' referenced before assignment 

The same rule applies to classes, but it seems to default to the global scope instead of raising an AttributeError:

a = "global" class C(): print(a) a = "local" # 'global' 

Moreover, in the case of a nested function, the behavior is the same (without using nonlocal or global) :

a = "global" def outer_func(): a = "outer" def inner_func(): print(a) a = "local" inner_func() outer_func() # UnboundLocalError: local variable 'a' referenced before assignment 

But in the case of nested classes, it still defaults to the global scope, and not the outer scope (again without using global or nonlocal) :

a = "global" def outer_func(): a = "outer" class InnerClass: print(a) a = "local" outer_func() # 'global' 

The weirdest part is that the nested class default to the outer scope when there is no declaration of a :

a = "global" def outer_func(): a = "outer" class InnerClass: print(a) outer_func() # 'outer' 

So my questions are :

  • Why the discrepancy between functions and classes (one raising an exception, the other defaulting to the global scope.
  • In nested classes, why the default scope has to become global instead of keeping using the outer one when using a variable defined afterward?
1
  • This is pretty much exactly how it's supposed to behave. What docs have you read? Commented Mar 25, 2021 at 11:33

1 Answer 1

3

The answer is given in great detail in Section 9.2 of the official docs. The crux of the matter is

... On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)

When you are in the class definition, which at the moment of its execution is the innermost scope, dynamic name resolution applies. You therefore see printouts of the global value of a.

If the name resolution were static, as in function definitions, the name a would be recognized as a local name even in the print statement. That is why you can't print a in a function before assigning to it.

The rules for class body scoping are alluded to in Section 4.2.2:

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.

Let's parse that last sentence carefully, because it fully covers your last two examples. First off, what is an unbound local variable in this context? A class body creates a new namespace, just like entering a function. If a name is bound somewhere in a class body, it is a local variable. This is determined statically, as mentioned above. If you attempt to reference the name before it is first bound, you have an unbound local variable. Instead of raising an error, as a function call would do, python jumps straight to the global namespace to perform the lookup (and ignores builtins as well). In all other cases (not local variables), normal LEGB lookup order applies.

This is indeed a bit counter-intuitive, and I would argue that it pushes if not outright breaks the rule of least surprise.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks! It does explain the difference between functions and classes, but what about the need to switch from outer scope to global scope?
@QuentinCoumes updated. But on second thought, my answer is trash
As shown by my last example, the inner class seems to be aware of the enclosing scope, as long as a variable is not redefined. Thanks for providing more information anyway.
@QuentinCoumes. I think I updated with the correct interpretation now.
We had a similar question recently, and I wondered why this exception existed. The "best" explanation I found comes from Guido, who explains that it is for compatibility with Python 2.1. I haven't found, though, why this behavior appeared at that time...
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.