Nonlocal
We say that a variable defined in a frame is local to that frame. A variable is nonlocal to a frame if it is defined in the environment that the frame belongs to but not the frame itself, i.e. in its parent or ancestor frame.
So far, we know that we can access variables in parent frames:
def make_adder(x):
""" Returns a one-argument function that returns the result of
adding x and its argument. """
def adder(y):
return x + y
return adder
Here, when we call make_adder
, we create a function adder
that is able to look up the name x
in make_adder
's frame and use its value.
However, we haven't been able to modify
variable in parent frames.
Consider the following function:
def make_withdraw(balance):
"""Returns a function which can withdraw
some amount from balance
>>> withdraw = make_withdraw(50)
>>> withdraw(25)
25
>>> withdraw(25)
0
"""
def withdraw(amount):
if amount > balance:
return "Insufficient funds"
balance = balance - amount
return balance
return withdraw
The inner function withdraw
attempts to update the variable balance in its parent frame. Running this function's doctests, we find that it causes the following error:
UnboundLocalError: local variable 'balance' referenced before assignment
Why does this happen?
When we execute an assignment statement, remember that we are either creating a new binding in our current frame or we are updating an old one in the current frame.
For example, the line balance = ...
in withdraw
, is creating the local variable balance inside withdraw
's frame.
This assignment statement tells Python to expect a variable called balance inside withdraw
's frame, so Python will not look in parent frames for this variable.
However, notice that we tried to compute balance - amount
before the local variable was created!
That's why we get the UnboundLocalError
.
To avoid this problem, we introduce the nonlocal
keyword.
It allows us to update a variable in a parent frame!
Some important things to keep in mind when using
nonlocal
nonlocal
cannot be used with global variables (names defined in the global frame). If nononlocal
variable is found with the given name, aSyntaxError
is raised. A name that is already local to a frame cannot be declared asnonlocal
.
Consider this improved example:
def make_withdraw(balance):
"""Returns a function which can withdraw
some amount from balance
>>> withdraw = make_withdraw(50)
>>> withdraw(25)
25
>>> withdraw(25)
0
"""
def withdraw(amount):
nonlocal balance
if amount > balance:
return "Insufficient funds"
balance = balance - amount
return balance
return withdraw
The line nonlocal balance
tells Python that balance will not be local to this frame, so it will look for it in parent frames.
Now we can update balance
without running into problems.