Python: Function Scope

On each call to a function a local namespace is created which includes the names of the function arguments and the variables locally assigned within the function. Other variables accessed in the function belong to (enclosing) global namespace. The method globals() returns the dict __globals__ which is the global namespace. The method locals() concocts and returns a dict containing local namespace including closure variables. A local variable cannot be modified by just changing the dict returned by locals(). The exec() executes within the global and local namespaces of the caller. However, changes to local variables by exec() has no effect.

Two types of name related errors:

  1. NameError: while looking up names in global namespace
  2. UnboundedLocalError: while looking up a local variable not yet assigned a value
def func(x):
    if x > 0:
        y = 42
    return x+y # if x <= 0, y is not assigned

func(10) # returns 52
func(-10) # UnboundedLocalError: y referenced before assignment
def func():
    n += 1 # UnboundedLocalError

A variable name never changes scope—it’s either a global variable or it’s a local variable and this is determined at function definition time.

x = 42
def func():
    print(x) # UnboundedLocalError
    # Assignment of x here made it local scope at the function definition time
    x = 13

func()
x = 42
def func():
    # x here is local and is a completely different object than the global x(=42)
    x = 13

func()
# x is still 42

global statement can be used to modify a global variable but better to use a class to modify state.

x = 42
y = 37
def func():
    global x
    x = 13
    y = 0

func()
# x is 13 but y is still 37

Less sneaky:

class Config:
    x = 42

def func():
    Config.x = 13

For nested functions, name resolution goes from innermost scope to outermost scope. An inner function cannot reassign a variable assigned in an outer function.

def countdown(start):
    n = start
    def display():
        print('T-minus', n)
    def decrement():
        n -= 1 # UnboundedLocalError
    while n > 0:
        display()
        decrement()

Using nonlocal statement can fix:

def countdown(start):
    n = start
    def display():
        print('T-minus', n)
    def decrement():
        nonlocal n
        n -= 1 # modifies the outer n
    while n > 0:
        display()
        decrement()

Nested functions and nonlocal declarations are not common. Inner functions are not visible from outside which complicates testing and debugging. But nested functions may help break complex calculations into small chunks, hiding internal implementation details.

Leave a comment