UnboundLocalError: Local Variable Referenced Before Assignment in Python

UnboundLocalError (“local variable referenced before assignment”) means a function reads a variable that Python has decided is local, before that local variable has been given a value. It almost always happens because the function assigns to a name that also exists outside it.

visits = 0

def record_visit():
    visits += 1        # UnboundLocalError

# fix: pass it in and return it, or declare it global
def record_visit(visits):
    return visits + 1

Newer versions of Python, including the Python 3.12.5 I used for every screenshot here, word the same error as “cannot access local variable ‘visits’ where it is not associated with a value”.

Both messages mean the same thing. Below you’ll see why Python behaves this way, the five situations that trigger it, and which fix to pick.

What the UnboundLocalError traceback looks like

Here’s the classic version: a counter at the top of the file, and a function that adds one to it.

visits = 0

def record_visit():
    visits += 1          # reads visits AND assigns to it
    return visits

record_visit()

Traceback:

Traceback (most recent call last):
  File "C:\pyguides\unbound_error.py", line 7, in <module>
    record_visit()
  File "C:\pyguides\unbound_error.py", line 4, in record_visit
    visits += 1          # reads visits AND assigns to it
    ^^^^^^
UnboundLocalError: cannot access local variable 'visits' where it is not associated with a value
Command Prompt showing a Python UnboundLocalError traceback cannot access local variable visits where it is not associated with a value
The traceback points at visits += 1, the line that reads the variable.

It looks like it should work. You can see visits right there, set to 0. So why does Python tell you it has no value?

Why does Python treat the variable as local?

Python decides which names are local when it compiles the function, not while it runs it. If a name is assigned anywhere in the function, it’s local everywhere in that function, even on the lines above the assignment.

visits += 1 is an assignment, so visits became local. Then it tried to read the local visits to add one, and there wasn’t one yet.

You can watch this happen. In the next example, the print() comes first, and it still fails because of the line after it:

price = 19.99

def show_price():
    print(price)         # looks like it reads the global...
    price = 24.99        # ...but this line makes price local for the WHOLE function

try:
    show_price()
except UnboundLocalError as err:
    print("UnboundLocalError:", err)

def only_reads():
    return price         # no assignment here, so the global is used

print("only_reads():", only_reads())
print("local names in show_price:", show_price.__code__.co_varnames)
print("local names in only_reads:", only_reads.__code__.co_varnames)

Output:

UnboundLocalError: cannot access local variable 'price' where it is not associated with a value
only_reads(): 19.99
local names in show_price: ('price',)
local names in only_reads: ()
Command Prompt output showing a Python function that assigns to price later fails on the earlier print, and co_varnames listing price as local
co_varnames lists the function’s local names: price is local in one function and absent from the other.

A function that only reads price finds the global one with no trouble. The assignment is what changes the rules, which is also why this is a different error from NameError: name is not defined.

Fix 1: pass the value in and return it

This is the fix I’d reach for first. The function gets what it needs as an argument and hands the new value back, so it doesn’t depend on anything outside itself:

def record_visit(visits):
    return visits + 1      # take the value in, hand the new value back

visits = 0
visits = record_visit(visits)
visits = record_visit(visits)
print("visits:", visits)

Output:

visits: 2

It’s a bit more typing, but you get a function that’s easy to test and can’t quietly change something elsewhere in your program.

Fix 2: make the variable global

If the function genuinely should change a module-level variable, say so with global:

visits = 0

def record_visit():
    global visits          # "visits" now means the module-level name
    visits += 1

record_visit()
record_visit()
print("visits:", visits)

Output:

visits: 2

global visits tells Python not to create a local. It’s fine for a small script, but in a bigger program, globals make it hard to tell who changed what. Setting global variables in functions covers the details.

Fix 3: use nonlocal in a nested function

When the outer variable belongs to an enclosing function rather than the module, global is the wrong keyword. Use nonlocal:

def make_counter():
    count = 0
    def tick():
        nonlocal count     # use the enclosing function's count
        count += 1
        return count
    return tick

tick = make_counter()
tick()
tick()
print("ticks:", tick())

Output:

ticks: 3

Without nonlocal, count += 1 inside tick() raises the same UnboundLocalError, because the assignment makes count local to tick.

UnboundLocalError when a variable is assigned only inside an if

This version has nothing to do with globals. The variable only gets a value on one branch, so the other branch reaches the return with nothing assigned:

def shipping_cost(order_total):
    if order_total < 50:
        cost = 7.99
    return cost              # never assigned when the order is $50 or more

print("$20 order:", shipping_cost(20))

try:
    print("$80 order:", shipping_cost(80))
except UnboundLocalError as err:
    print("UnboundLocalError:", err)

def shipping_cost_fixed(order_total):
    cost = 0.00              # a value on every path
    if order_total < 50:
        cost = 7.99
    return cost

print("$80 order, fixed:", shipping_cost_fixed(80))

Output:

$20 order: 7.99
UnboundLocalError: cannot access local variable 'cost' where it is not associated with a value
$80 order, fixed: 0.0
Command Prompt output of a Python shipping cost function raising UnboundLocalError when the if branch that assigns cost is skipped
A $20 order works. An $80 order skips the if, so cost never exists.

Give the variable a value before the if, or add an else that assigns it. Either way, make sure every path through your function sets it.

UnboundLocalError after an except block

Here’s one that surprises experienced developers. The name you give an exception with as err is deleted when the except block ends:

def parse_price(text):
    try:
        return float(text)
    except ValueError as err:
        pass
    print("could not parse:", err)     # err was deleted when the except block ended

try:
    parse_price("$19.99")
except UnboundLocalError as e:
    print("UnboundLocalError:", e)

def parse_price_fixed(text):
    try:
        return float(text)
    except ValueError as err:
        problem = err                   # keep it under another name
    print("could not parse:", problem)

parse_price_fixed("$19.99")

Output:

UnboundLocalError: cannot access local variable 'err' where it is not associated with a value
could not parse: could not convert string to float: '$19.99'
Command Prompt output showing a Python exception variable from except as err raising UnboundLocalError after the except block ends
err existed inside the except block and was gone one line later.

Python does this on purpose, so your program doesn’t keep the whole traceback alive in memory. If you need the exception later, copy it to another name inside the block.

Catching UnboundLocalError with except NameError

UnboundLocalError is a subclass of NameError, so an except NameError handler catches both:

print("UnboundLocalError is a NameError:", issubclass(UnboundLocalError, NameError))

total = 0

def add(amount):
    total += amount

try:
    add(5)
except NameError as err:            # catches UnboundLocalError too
    print(type(err).__name__, "->", err)

Output:

UnboundLocalError is a NameError: True
UnboundLocalError -> cannot access local variable 'total' where it is not associated with a value

That’s useful to know when you read other people’s error handling, but I wouldn’t catch this error in real code. It’s a bug in the function, and the fix belongs in the function.

Which UnboundLocalError fix should you use?

SituationFix
A function updates a module-level counter or totalPass it in and return it; use global only in small scripts
A nested function updates the outer function’s variablenonlocal name
The variable is set inside an if, for or try onlyGive it a value before the block
You use an exception after its except blockCopy it to another name inside the block
You meant to read the global, not create a localRename the local variable

Keep going with these related Python guides:

Frequently asked questions

What does ‘local variable referenced before assignment’ mean?

The function assigns to a variable, which makes it local, but reads it before that assignment has run. Python raises UnboundLocalError instead of falling back to a global with the same name.

Why does += cause UnboundLocalError?

x += 1 both reads and assigns x. The assignment makes x local to the function, so the read finds an empty local variable.

How do I fix UnboundLocalError?

Pass the value in as an argument and return the result, declare it with global or nonlocal, or make sure every path assigns the variable before it’s used.

What’s the difference between UnboundLocalError and NameError?

NameError means the name doesn’t exist anywhere Python looks. UnboundLocalError means it’s a local name that hasn’t been assigned yet. UnboundLocalError is a subclass of NameError.

Why does my error say ‘cannot access local variable’ instead?

That’s the wording in newer versions of Python. It’s the same UnboundLocalError as ‘local variable referenced before assignment’.

Is using global a bad fix?

It works, and it’s fine in short scripts. In larger programs, passing values in and returning them keeps functions easier to test and reason about. The Python FAQ explains the rule behind this error.