Search code examples
pythonpython-3.xscopeclass-variables

Python: what scopes are accessible when defining class variables?


Consider these two python files:

# file1.py
global_var = "abc"

class A:
    x = 1
    glb = global_var
    y = x + 1
    class B:
        z = 3
        glb = global_var
    zz = B.z

print(f"{A.B.z=}")
print(f"{A.zz=}")
# file2.py
global_var = "abc"

class A:
    x = 1
    glb = global_var
    y = x + 1
    class B:
        z = y + 1
        glb = global_var
    zz = B.z

print(f"{A.B.z=}")
print(f"{A.zz=}")

One would expect them to do the exact same thing. But they don't!

$ python file1.py
A.B.z=3
A.zz=3
$ python file2.py
Traceback (most recent call last):
  File "file2.py", line 4, in <module>
    class A:
  File "file2.py", line 8, in A
    class B:
  File "file2.py", line 9, in B
    z = y + 1
NameError: name 'y' is not defined

Questions:

  • Why can the definition of B access the global scope, but not the scope of A?
  • Why is it that y = x + 1 should work but z = y + 1 should not? Is this a design decision, or undefined behavior of CPython?
  • What are the general rules for what variables / scope are accessible when calculating the values of class variables? When should I worry about which scope I'm allowed to use in defining my class variables?

Solution

  • From https://docs.python.org/3/reference/executionmodel.html:

    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. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:

    In the definition of B, y is an unbound local variable, and so is looked up in the global scope (where it is not defined), not in the namespace created by the enclosing class statement.

    A class statement does not define a scope at all; it creates a namespace which is passed to the metaclass in order to construct a new class.