UnboundLocalError means Python treated a name as local to a function, but the function tried to read it before that local name had been given a value. This can happen even when a global or enclosing variable with the same name already has a value: an assignment anywhere in the function normally makes the name local throughout that function.
Why does Python raise UnboundLocalError?
Python determines a function’s local names by examining its code block, not by checking which branch happens to run first. If a name is assigned anywhere in a function, Python treats references to that name in the function as references to its local binding—unless the function declares it global or nonlocal.
As an Amazon Associate I earn from qualifying purchases.
If execution reaches a read before that local binding has a value, Python raises UnboundLocalError. The language reference describes this as a local variable “that has not yet been bound to a value at the point where the name is used.” Python’s execution-model documentation explains the rule. UnboundLocalError is a subclass of NameError. The built-in exceptions reference documents the exception.
Free tools Windows power users keep installed
One-click scans. No signup required.
A global value can still be shadowed
In this example, the module-level x does not supply a value for the read inside foo:
#1 Best Overall
x = 10
def foo():
print(x)
x += 1
foo()
The augmented assignment x += 1 binds x locally in foo. As a result, print(x) also refers to that local name, which has not yet been assigned a value. Python raises the error rather than falling back to the global x. A later ordinary assignment can create the same problem in a function that previously only read an outer variable. The Python Programming FAQ provides this example and explains the shadowing behavior.
Conditional assignments can leave a local uninitialized
A name assigned only in one branch is still local throughout the function. If another path reaches a read without running that assignment, the local has no value:
def label(is_ready):
if is_ready:
result = "ready"
print(result)
Calling label(False) reaches the print without binding result. The issue is not that Python failed to find an outer value; the function’s own binding exists in the scope classification but has not been initialized on this path.
Recommended Free Tools
Rank #2
The same reasoning applies when assignments sit in loops, exception handlers, or other control-flow branches: inspect whether the path that fails actually executes a binding before the read.
How to debug the error
- Read the traceback. Find the function and exact variable named in the final exception line. Wording varies by Python version: Python 3.14.7 documentation shows
cannot access local variable 'x' where it is not associated with a value, while older documentation showslocal variable 'x' referenced before assignment. See the Python FAQ. - Search the entire function for bindings. Look for ordinary assignments, augmented assignments such as
+=, and assignments inside branches or handlers. A binding anywhere in the function can make the name local. - Decide where the state belongs. Is it a value owned by this function, a module-level binding, or a binding in an enclosing function? The right fix depends on that intent.
- Check every relevant path. Follow branches, early returns, loops, and exception paths to see whether a local value is guaranteed before each read.
- Apply the matching fix and rerun the failing path. Also exercise paths that skip conditional assignments; the path that raised the error is the one that needs verification.
For interactive diagnosis, the Python FAQ identifies breakpoint(), pdb, and IDLE’s graphical debugger. It also names Visual Studio Code, Wing IDE, and PyCharm as IDEs with debuggers. For static checks, it lists Ruff, Pylint, and Pyflakes as basic bug-finding tools. These are options, not prerequisites; see the FAQ’s debugging guidance.
Which fix should you use?
Choose based on where the value should live and whether the function is supposed to rebind it. Initializing a local keeps the state within the function; scope declarations intentionally alter an outer binding. Passing a value in and returning a result often makes the data flow clearest.
| Intended ownership | Fix | Effect |
|---|---|---|
| Local to this function | Initialize it before any possible read | Keeps ownership and updates inside the function. |
| Module-level binding | Declare global name in the function |
Makes assignments rebind the module-level name. |
| Binding in an enclosing function | Declare nonlocal name in the nested function |
Makes assignments rebind the existing name in the nearest enclosing function scope. |
| State that need not be rebound in an outer scope | Pass it as a parameter, return a new value, or restructure around an explicit local | Makes inputs and outputs explicit rather than changing outer-scope state. |
Initialize a local when the function owns the value
Give the local a value before it can be read, on every path that reaches the read. For example:
def label(is_ready):
result = "not ready"
if is_ready:
result = "ready"
print(result)
Before relying on a default, check that it is appropriate for all paths; initialization should represent the function’s intended behavior, not merely silence the exception.
Use global only for an intended module-level update
If the function is meant to rebind a module-level name, declare it before using that name in the function:
x = 10
def foo():
global x
print(x)
x += 1
The declaration changes where assignments to x take effect. Use it when that shared module state is intentional, not as a generic repair for a missing local initialization.
Use nonlocal for an enclosing function’s binding
For a nested function that should update a name belonging to an enclosing function, use nonlocal:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsdef make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
Here count belongs to make_counter; increment intentionally rebinds that enclosing function’s name. The FAQ demonstrates both global and nonlocal as scope-specific solutions: Python Programming FAQ.
Best Value
Prefer explicit data flow when outer rebinding is unnecessary
If a function only needs a value to calculate a result, accept it as a parameter and return the updated value instead of mutating a global or enclosing binding:
def increment(value):
return value + 1
x = increment(x)
This makes the value’s source and destination visible at the call site and avoids changing outer-scope state from inside the function.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




