In Python, the nonlocal
keyword allows code in an inner scope to access a name in the enclosing scope.
To explain this, we can observe the behaviour of this example code, taken from the Python tutorial documentation.
def scope_test():
def do_nonlocal():
nonlocal spam
spam = 'nonlocal spam'
spam = 'test spam'
do_nonlocal()
print(f'after nonlocal assignment spam={spam}')
scope_test()
# prints: 'after nonlocal assignment spam=nonlocal spam'
This demonstrates that the nonlocal
keyword can be used to access the name spam
from the enclosing scope, rather than the scope defined by do_nonlocal
.
Can nonlocal
be used recursively to access progressively higher levels of scope?
The answer is yes. Before providing a demonstration example, it is helpful to annotate some code to show where the various scopes exist.
# global scope exists here
def spam_function():
# the function definition `spam_function` creates a new "local"
# scope with the same name
def inner_spam_function():
# this function definition creates yet another scope, with
# the name `inner_spam_function`. this scope/namespace is
# nested within the `spam_function` (enclosing) scope
def inner_inner_spam_function():
# finally, yet another level of nested function
# this namespace `inner_inner_spam_function` lives inside
# global/spam_function/inner_spam_function/inner_inner_spam_function
It is useful to know that a function definition itself creates a new level of scope. (A new namespace.) Namespaces in Python are essentially a tree type data structure.
Finally, here is the demonstration example:
def spam_function():
spam = 'spam function spam'
def inner_spam_function():
nonlocal spam
spam = 'inner spam function'
def inner_inner_spam_function():
nonlocal spam
spam = 'inner inner spam function - wow'
inner_inner_spam_function()
inner_spam_function()
print(f'spam={spam}')
spam_function()
# this will print: `spam=inner inner spam function - wow`
Finally, it might be useful to note that without nonlocal
, assignment creates a new name, whereas reading a value looks in the enclosing scope(s) to resolve the name.
def outer_function():
x = 10
def inner_function_with_assignment():
x = 11 # writing creates a new name `x`
inner_function_with_assignment()
print(x)
# prints 10
def inner_function_with_read():
y = x # x is resolved to x in scope 1 level higher
print(y)
inner_function_with_read()
# prints 10
def nested_inner_function_with_read_1():
def nested_inner_function_with_read_2():
z = x # x is resolved to x in scope 2 levels higher
print(z)
nested_inner_function_with_read_2()
nested_inner_function_with_read_1()
# prints 10
outer_function()