Lists and dictionaries have local state: they are changing values that have some particular contents at any point in the execution of a program. The word "state" implies an evolving process in which that state may change.

Functions can also have local state. For instance, let us define a function that models the process of withdrawing money from a bank account. We will create a function called withdraw, which takes as its argument an amount to be withdrawn. If there is enough money in the account to accommodate the withdrawal, then withdraw will return the balance remaining after the withdrawal. Otherwise, withdraw will return the message 'Insufficient funds'. For example, if we begin with $100 in the account, we would like to obtain the following sequence of return values by calling withdraw:

>>> withdraw(25)
75
>>> withdraw(25)
50
>>> withdraw(60)
'Insufficient funds'
>>> withdraw(15)
35

Above, the expression withdraw(25), evaluated twice, yields different values. Thus, this user-defined function is non-pure. Calling the function not only returns a value, but also has the side effect of changing the function in some way, so that the next call with the same argument will return a different result. This side effect is a result of withdraw making a change to a name-value binding outside of the current frame.

For withdraw to make sense, it must be created with an initial account balance. The function make_withdraw is a higher-order function that takes a starting balance as an argument. The function withdraw is its return value.

>>> withdraw = make_withdraw(100)

An implementation of make_withdraw requires a new kind of statement: a nonlocal statement. When we call make_withdraw, we bind the name balance to the initial amount. We then define and return a local function, withdraw, which updates and returns the value of balance when called.

>>> def make_withdraw(balance):
        """Return a withdraw function that draws down balance with each call."""
        def withdraw(amount):
            nonlocal balance                 # Declare the name "balance" nonlocal
            if amount > balance:
                return 'Insufficient funds'
            balance = balance - amount       # Re-bind the existing balance name
            return balance
        return withdraw

The nonlocal statement declares that whenever we change the binding of the name balance, the binding is changed in the first frame in which balance is already bound. Recall that without the nonlocal statement, an assignment statement would always bind a name in the first frame of the current environment. The nonlocal statement indicates that the name appears somewhere in the environment other than the first (local) frame or the last (global) frame.

The following environment diagrams illustrate the effects of multiple calls to a function created by make_withdraw.

1def make_withdraw(balance):
2    def withdraw(amount):
3        nonlocal balance
4        if amount > balance:
5            return 'Insufficient funds'
6        balance = balance - amount
7        return balance
8    return withdraw
9
10wd = make_withdraw(20)
11wd(5)
12wd(3)
Step 6 of 15
line that has just executed

next line to execute

Global
make_withdraw
 
wd
 
f1: make_withdraw [parent=Global]
balance20
withdraw
 
Return
value
 
func make_withdraw(balance) [parent=Global]
func withdraw(amount) [parent=f1]

The first def statement has the usual effect: it creates a new user-defined function and binds the name make_withdraw to that function in the global frame. The subsequent call to make_withdraw creates and returns a locally defined function withdraw. The name balance is bound in the parent frame of this function. Crucially, there will only be this single binding for the name balance throughout the rest of this example.

Next, we evaluate an expression that calls this function, bound to the name wd, on an amount 5. The body of withdraw is executed in a new environment that extends the environment in which withdraw was defined. Tracing the effect of evaluating withdraw illustrates the effect of a nonlocal statement in Python: a name outside of the first local frame can be changed by an assignment statement.

1def make_withdraw(balance):
2    def withdraw(amount):
3        nonlocal balance
4        if amount > balance:
5            return 'Insufficient funds'
6        balance = balance - amount
7        return balance
8    return withdraw
9
10wd = make_withdraw(20)
11wd(5)
12wd(3)
Step 11 of 15
line that has just executed

next line to execute

Global
make_withdraw
 
wd
 
f1: make_withdraw [parent=Global]
balance15
withdraw
 
Return
value
 
f2: withdraw [parent=f1]
amount5
Return
value
15
func make_withdraw(balance) [parent=Global]
func withdraw(amount) [parent=f1]

The nonlocal statement changes all of the remaining assignment statements in the definition of withdraw. After executing nonlocal balance, any assignment statement with balance on the left-hand side of = will not bind balance in the first frame of the current environment. Instead, it will find the first frame in which balance was already defined and re-bind the name in that frame. If balance has not previously been bound to a value, then the nonlocal statement will give an error.

By virtue of changing the binding for balance, we have changed the withdraw function as well. The next time it is called, the name balance will evaluate to 15 instead of 20. Hence, when we call withdraw a second time, we see that its return value is 12 and not 17. The change to balance from the first call affects the result of the second call.

1def make_withdraw(balance):
2    def withdraw(amount):
3        nonlocal balance
4        if amount > balance:
5            return 'Insufficient funds'
6        balance = balance - amount
7        return balance
8    return withdraw
9
10wd = make_withdraw(20)
11wd(5)
12wd(3)
End
line that has just executed

next line to execute

Global
make_withdraw
 
wd
 
f1: make_withdraw [parent=Global]
balance12
withdraw
 
Return
value
 
f2: withdraw [parent=f1]
amount5
Return
value
15
f3: withdraw [parent=f1]
amount3
Return
value
12
func make_withdraw(balance) [parent=Global]
func withdraw(amount) [parent=f1]

The second call to withdraw does create a second local frame, as usual. However, both withdraw frames have the same parent. That is, they both extend the environment for make_withdraw, which contains the binding for balance. Hence, they share that particular name binding. Calling withdraw has the side effect of altering the environment that will be extended by future calls to withdraw. The nonlocal statement allows withdraw to change a name binding in the make_withdraw frame.

Ever since we first encountered nested def statements, we have observed that a locally defined function can look up names outside of its local frames. No nonlocal statement is required to access a non-local name. By contrast, only after a nonlocal statement can a function change the binding of names in these frames.

By introducing nonlocal statements, we have created a dual role for assignment statements. Either they change local bindings, or they change nonlocal bindings. In fact, assignment statements already had a dual role: they either created new bindings or re-bound existing names. Assignment can also change the contents of lists and dictionaries. The many roles of Python assignment can obscure the effects of executing an assignment statement. It is up to you as a programmer to document your code clearly so that the effects of assignment can be understood by others.

Python Particulars. This pattern of non-local assignment is a general feature of programming languages with higher-order functions and lexical scope. Most other languages do not require a nonlocal statement at all. Instead, non-local assignment is often the default behavior of assignment statements.

Python also has an unusual restriction regarding the lookup of names: within the body of a function, all instances of a name must refer to the same frame. As a result, Python cannot look up the value of a name in a non-local frame, then bind that same name in the local frame, because the same name would be accessed in two different frames in the same function. This restriction allows Python to pre-compute which frame contains each name before executing the body of a function. When this restriction is violated, a confusing error message results. To demonstrate, the make_withdraw example is repeated below with the nonlocal statement removed.

1def make_withdraw(balance):
2    def withdraw(amount):
3        if amount > balance:
4            return 'Insufficient funds'
5        balance = balance - amount
6        return balance
7    return withdraw
8
9wd = make_withdraw(20)
10wd(5)
Step 8 of 9
UnboundLocalError: local variable 'balance' referenced before assignment
line that has just executed

next line to execute

Global
make_withdraw
 
wd
 
f1: make_withdraw [parent=Global]
balance20
withdraw
 
Return
value
 
f2: withdraw [parent=f1]
amount5
func make_withdraw(balance) [parent=Global]
func withdraw(amount) [parent=f1]

This UnboundLocalError appears because balance is assigned locally in line 5, and so Python assumes that all references to balance must appear in the local frame as well. This error occurs before line 5 is ever executed, implying that Python has considered line 5 in some way before executing line 3. As we study interpreter design, we will see that pre-computing facts about a function body before executing it is quite common. In this case, Python's pre-processing restricted the frame in which balance could appear, and thus prevented the name from being found. Adding a nonlocal statement corrects this error. The nonlocal statement did not exist in Python 2.