How to use (access or modify) variables from different scopes in Python

On nonlocal, global and closures

Pavol Kutaj
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 binding message 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:
  1. even if a local function contains a call to an existing binding in an enclosed/global scope
  2. 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

--

--

No responses yet