Today we’ll be talking about the nonlocal statement.
This article is sort of complementary to my article on the global statement and to my article on scopes in general, so feel free to read them first to have some background.
If you prefer to watch a video version, here it is:
Just as the global statement gives a function access to global variables, so does the nonlocal statement give nested functions access to variables in enclosing functions.
There is one difference, the nonlocal statement can’t be used to create a variable in nonlocal scope. We can read variables from nonlocal scope and write to them, but not create them in nested functions. This means, the variable must be first defined in an enclosing function before it can be used.
without the nonlocal statement
Before we see how the nonlocal statement works, let’s have a look at an example where it’s not used.
Here we have a nested function that tries to change the value of a variable defined in an enclosing function:
def func():
x = 10
def fNested():
x = 2
print(f"x in nested function: {x}")
fNested()
print(f"x in enclosing function: {x}")
func()
Here’s the output:
x in nested function: 2
x in enclosing function: 10
As you can see, the assignment to x in the nested function did not change the value of x in the enclosing function. Instead it created a new local variable with the same name.
with the nonlocal statement
If our goal is to be able to change the variable in the enclosing function, we have to add the nonlocal statement like so:
def func():
x = 10
def fNested():
nonlocal x
x = 2
print(f"x in nested function: {x}")
print(f"x in enclosing function before calling the nested function: {x}")
fNested()
print(f"x in enclosing function after being changed in the nested function: {x}")
func()
Now the output is:
x in enclosing function before calling the nested function: 10
x in nested function: 2
x in enclosing function after being changed in the nested function: 2
As you can see, now the variable from the enclosing function indeed changed in the nested function.
Example with a Closure
And now let’s have a look at an example with a closure.
If you’re not sure what closures are, I have an article on closures, so feel free to read it.
To be brief, a closure is a function that remembers the value from its enclosing function even after the enclosing function has returned. Here we have an example of a closure with the nonlocal statement:
>>> def countdown():
... counter = 1
... def lastWord(word):
... nonlocal counter
... for n in range(counter, 0, -1):
... print(f"{n}", end = "...")
... print(word)
... counter += 1
... return lastWord
...
The counter variable is defined in the enclosing function, which is the nonlocal scope for the nested function.
In the nested function we use the nonlocal statement. This means that from now on we’ll be using the variable from the enclosing function directly.
Now, again, if you don’t understand how this functions work, make sure to read my article on closures.
Anyway, the countdown function returns the nested lastWord function. What’s important it just RETURNS the nested function, but it doesn’t CALL it. This way we can assign the function object to a variable:
>>> c = countdown()
Now c is the lastWord function and we can use it like so:
>>> c("go")
1...go
What’s interesting the lastWord function still remembers the value to which counter was set in the enclosing function. What’s more, the nested function now has access to that variable and changes it by incrementing it by 1. So, the next time the function c is called, the value of the counter will be one more:
>>> c("run")
2...1...run
>>> c("faster!")
3...2...1...faster!
>>> c("speed")
4...3...2...1...speed
>>> c("gallop")
5...4...3...2...1...gallop