Painful details of variable scope mixed with classes

In code that’s compiled to use optimized (i.e. fast) locals, dynamic locals and import * are not supported, and if a variable is local, then it’s always local. Note that in this case, trying to reference a local variable before it’s assigned, or after it’s deleted, will raise UnboundLocalError. With this knowledge in hand, the compiler knows that a free variable that’s not from an enclosing function scope can be assumed to be in globals or builtins. It thus uses the opcode LOAD_GLOBAL, which checks f_globals and f_builtins.

The above is in contrast to code that’s compiled to use non-optimized locals (i.e. locals stored in the f_locals mapping), such as the top-level code of a class body, module, or exec(). In this case, the scope of a name can change fluidly between locals and globals, or locals and nonlocals. Also, dynamic locals are supported, as well as import * usually (except not in a class body). Unless a variable is explicitly declared global, or is from an enclosing function scope, the compiler references it via LOAD_NAME, which checks f_locals, f_globals, and f_builtins, in that order. If a variable is explicitly declared global, then LOAD_GLOBAL is used instead of LOAD_NAME.

In a class body, a variable from an enclosing function scope may be referenced by the opcode LOAD_CLASSDEREF, which is a hybrid lookup. If the name isn’t defined in the f_locals mapping, the interpreter falls back on loading the cell object from the enclosing scope and dereferencing its contents. Note that if the name is assigned to as a local variable, then the compiler will reference the variable using LOAD_NAME instead of LOAD_CLASSDEREF, except if the assigned variable is declared nonlocal. For example:

def f():
    x = 'nonlocal 1'
    class C:
        nonlocal x
        print(x)
        x = 'nonlocal 2'
        print(x)
        x = 'nonlocal 3' 
        locals()['x'] = 'local' # dynamic assign
        print(x)
        del locals()['x'] # dynamic delete
        print(x)
>>> f()
nonlocal 1
nonlocal 2
local
nonlocal 3
2 Likes