Accessing variables defined in enclosing scope

Question:

From the Google Style Guide on lexical scoping:

A nested Python function can refer to variables defined in enclosing
functions, but can not assign to them.

Both of these seem to check out at first:

# Reference
def toplevel():
    a = 5
    def nested():
        print(a + 2)
    nested()
    return a
toplevel()
7
Out[]: 5

# Assignment
def toplevel():
    a = 5
    def nested():
        a = 7 # a is still 5, can't modify enclosing scope variable
    nested()
    return a
toplevel()
Out[]: 5

So why, then, does a combination of both reference and assignment in the nested function lead to an exception?

# Reference and assignment
def toplevel():
    a = 5
    def nested():
        print(a + 2)
        a = 7
    nested()
    return a
toplevel()
# UnboundLocalError: local variable 'a' referenced before assignment
Asked By: Brad Solomon

||

Answers:

In first case, you are referring to a nonlocal variable which is ok because there is no local variable called a.

def toplevel():
    a = 5
    def nested():
        print(a + 2) # theres no local variable a so it prints the nonlocal one
    nested()
    return a

In the second case, you create a local variable a which is also fine (local a will be different than the nonlocal one thats why the original a wasn’t changed).

def toplevel():
    a = 5 
    def nested():
        a = 7 # create a local variable called a which is different than the nonlocal one
        print(a) # prints 7
    nested()
    print(a) # prints 5
    return a

In the third case, you create a local variable but you have print(a+2) before that and that is why the exception is raised. Because print(a+2) will refer to the local variable a which was created after that line.

def toplevel():
    a = 5
    def nested():
        print(a + 2) # tries to print local variable a but its created after this line so exception is raised
        a = 7
    nested()
    return a
toplevel()

To achieve what you want, you need to use nonlocal a inside your inner function:

def toplevel():
    a = 5
    def nested():
        nonlocal a
        print(a + 2)
        a = 7
    nested()
    return a
Answered By: Mohd

For anyone stumbling across this question, in addition to the accepted answer here, it is answered concisely in the Python docs:

This code:

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

works, but this code:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

results in an UnboundLocalError.

This is because when you make an assignment to a variable in a scope,
that variable becomes local to that scope and shadows any similarly
named variable in the outer scope. Since the last statement in foo
assigns a new value to x, the compiler recognizes it as a local
variable. Consequently when the earlier print(x) attempts to print the
uninitialized local variable and an error results.

In the example above you can access the outer scope variable by
declaring it global:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
>>> foobar()
10

You can do a similar thing in a nested scope using the nonlocal
keyword:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
>>> foo()
10
11
Answered By: Brad Solomon
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.