UnboundLocalError

Any assignment to a name anywhere in a function makes it local for the whole function — so reading it before that assignment runs fails, even if a global of the same name exists.

InheritsBaseException›Exception›NameError›UnboundLocalError
Lookup exceptionPython 3 (all)Live demo
UnboundLocalError(*args)
Raised by
reading a local before assignment; x += 1 on a global
Message
cannot access local variable 'x' where it is not associated with a value
Quick fix
global x / nonlocal x, or pass it in and return it
Watch out
the assignment can be below the read — it still makes x local

Demo

Live evaluation
kind is assigned only inside the if. When the branch is skipped, return kind reads a local that never got a value.
Try:
Inputs
ninttry 0 or a negative
Code
def label(n):
    if n > 0:
        kind = 'positive'
    return kind

label(5)
Result
'positive'

In Trigger, a positive n works and 0 fails: kind is a local of label (it is assigned there), so Python never falls back to a global — it just finds the local empty. The message names the variable but not the reason; look for the paths where no assignment ran. Handle shows the other classic case fixed: without global count, count += step would raise the same error on every call.

Constructor

NameTypeRequiredDescription
*argsobjectnoUsually one message string. Unlike NameError, e.name is not filled in (it stays None).

Attributes

AttributeTypeMeaning
argstupleargs[0] is the message, e.g. "cannot access local variable 'x' where it is not associated with a value".
nameNoneInherited from NameError but not set for UnboundLocalError — parse args[0] if you need the variable name.

Common patterns

Initialise before branches
Give the local a value that covers every path, including the one where no branch or loop body runs.
def label(n):
    kind = 'non-positive'
    if n > 0:
        kind = 'positive'
    return kind
Closure counter with nonlocal
nonlocal rebinds a variable of the enclosing function instead of creating a new local.
def make_counter():
    n = 0
    def inc():
        nonlocal n
        n += 1
        return n
    return inc
Pass in, return out
Usually cleaner than global: the function gets the value as an argument and returns the new one.
def bump(count, step):
    return count + step

count = bump(count, 1)

Examples

1. Read before a later assignment
x = 'global' def show(): print(x) x = 'local' show()
Returns
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
2. Reading a global is fine
x = 'global' def show(): return x show()
Returns
'global'
3. Assignment only in try
def f(): try: value = int('abc') except ValueError: pass return value f()
Returns
UnboundLocalError: cannot access local variable 'value' where it is not associated with a value
4. Loop body never ran
def f(): for item in []: last = item return last f()
Returns
UnboundLocalError: cannot access local variable 'last' where it is not associated with a value
5. Mutating is not assigning
items = [] def add(v): items.append(v) return items add(1)
Returns
[1]
6. But += is assigning
items = [] def add(v): items += [v] return items add(1)
Returns
UnboundLocalError: cannot access local variable 'items' where it is not associated with a value
7. An import inside the function
import math def area(r): result = math.pi * r ** 2 import math return result area(1)
Returns
UnboundLocalError: cannot access local variable 'math' where it is not associated with a value
8. Caught as NameError
try: def f(): v += 1 f() except NameError as e: r = (type(e).__name__, e.name) r
Returns
('UnboundLocalError', None)

Pitfalls

1. Incrementing a global counter
count += 1 is count = count + 1: the assignment makes count local, and the read on the right-hand side happens first.
count += step
count = 10
def bump(step):
    count += step
    return count
bump(1)
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
global count
count = 10
def bump(step):
    global count
    count += step
    return count
bump(1)
11
2. Counter in a closure
The same rule applies to enclosing functions. global would be wrong here — the variable lives in make_counter, so use nonlocal.
n += 1
def make_counter():
    n = 0
    def inc():
        n += 1
        return n
    return inc
make_counter()()
UnboundLocalError: cannot access local variable 'n' where it is not associated with a value
nonlocal n
def make_counter():
    n = 0
    def inc():
        nonlocal n
        n += 1
        return n
    return inc
c = make_counter()
c()
c()
2
3. Shadowing a builtin inside a function
Assigning to len anywhere in the function makes len local, so even the call on the right-hand side fails.
len = len(...)
def f():
    len = len([1, 2])
    return len
f()
UnboundLocalError: cannot access local variable 'len' where it is not associated with a value
Pick another name
def f():
    size = len([1, 2])
    return size
f()
2

When to use

Use it
  • You rarely catch it — it signals a scoping bug to fix
  • except NameError already covers it when probing names dynamically
Reach for something else
  • Updating module state → global x (or better, return the new value)
  • Updating closure state → nonlocal x
  • Maybe-assigned locals → initialise before the if/try/for

Notes

Compile time
Whether a name is local is decided when the function is compiled: any assignment, augmented assignment, for target, import, del or with ... as in the body makes it local (see f.__code__.co_varnames)
Older message
Python 3.10 and earlier said local variable 'x' referenced before assignment
Free variables
If the enclosing function's variable is unbound, the error is NameError: cannot access free variable 'x' where it is not associated with a value in enclosing scope
Catch via
except NameError catches it (it is a subclass)

FAQ

The function assigns to that name somewhere, which makes it a local variable for the whole function body. At the moment the read ran, no assignment had happened yet — either it comes later in the code, or it sits in an if, try or loop body that did not run. Python does not fall back to a global of the same name.