How to use (access or modify) variables from different scopes in Python
On nonlocal, global and closures
3 min readMar 22, 2022
The aim of this page📝is to describe the use of nonlocal
keyword, its relation to an analogous global
, its difference to closures, its indifference to LEGB rule and finally on the error
local variable '<binding>' referenced before assignment
1. NONLOCAL VS CLOSURES
- the following piece of code creates a counter starting at given value
nonlocal
introduces-for-reassignment an existing binding from enclosed scope
def make_counter(n):
def incr():
nonlocal n #introduce `n` from L1
n = n + 1 #re-assign/modify that value from enclosing scope
print(n)
return incr
- on the other hand, the following one prints the value of enclosed binding
- closures introduce-for-reference — objects close over values defined in enclosed/global scopes
def enclosing():
x = 'closed over'
def local_func():
print(x) # refer to `x` from L2; no assignment operator (`=`)
return local_func
2. LEGB AND ASSIGNMENTS
- NOTE: LEGB does not apply when we make/assign a new name binding
- LEGB is designed for referring not for (re)assigning
- below,
local()
creates an entirely new name binding which has nothing to do with other scopes - below,
local()
creates a new frame/scope with the named bindingmessage
applying only there - the presence of
=
is critical in this dynamic
message = "global"
def enclosing():
message = "enclosing"
def local():
message = "local"
print("enclosing message:", message)
local()
print("enclosing mesage", message)
print("global message", message)
enclosing()
print("global message", message)
""" OUTPUT """
# global message global
# enclosing message: enclosing
# enclosing mesage enclosing
# global message global
3. LOCAL VARIABLE ‘’ REFERENCED BEFORE ASSIGNMENT
- CONDITIONS:
- even if a local function contains a call to an existing binding in an enclosed/global scope
- and if there also is an assignment of that exact name
- RESULT: the call will fail with
local variable 'message' referenced before assignmentmessage = "global"
def enclosing():
message = "enclosing"
def local():
print("from local", message) # THIS WILL NOT PRINT "enclosing" BECAUSE OF THE NEXT LINE
message = "local" # DELETE THIS AND THE ABOVE LINE WILL PRINT "enclosing"s
print("enclosing message:", message)
local()
print("enclosing mesage", message)
print("global message", message)
enclosing()
print("global message", message)
""" OUTPUT """
# global message global
# enclosing message: enclosing
# Traceback (most recent call last):
# ...
# UnboundLocalError: local variable 'message' referenced before assignment
4. GLOBAL KEYWORD
- Note — there already is Global-Keyword that introduces bindings from
global
scope into a local scope - once introduced, the global binding can be used/accessed/modified locally
message = "global"
def enclosing():
message = "enclosing"
def local():
global message
message = "local"
print("enclosing message:", message)
local()
print("enclosing mesage", message)
""" CALL """
print("global message", message)
enclosing()
print("global message", message)
""" OUTPUT """
# global message global
# enclosing message: enclosing
# enclosing mesage enclosing
# global message local <<< THE GLOBAL BINDING WAS CHANGED IN LOCAL FUNCTION
5. NONLOCAL KEYWORD
- inserts a name binding from an enclosing scope into the local namespace
- searches enclosing scopes from innermost > outermost
- as soon as it finds a match, that binding is introduced to the scope where
nonlocal
is invoked - from that point, the local function is modifying binding in enclosing scopes
nonlocal
is used to keep state / create a form of persistent storage
message = "global"
def enclosing():
message = "enclosing"
def local():
nonlocal message
message = "local"
print("enclosing message:", message)
local()
print("enclosing mesage", message)
""" CALL """
print("global message", message)
enclosing()
print("global message", message)
""" OUTPUT """
# global message global
# enclosing message: enclosing
# enclosing mesage local <<< MODIFIED BY `nonlocal` KEYWORD
# global message global
- NOTE: If no matching nonlocal binding is found, Python returns a syntax error