Fix UnboundLocalError: Local Variable Referenced Before Assignment

UnboundLocalError: local variable referenced before assignment occurs when a function reads a name before that local name has received a value. Python decides which names are local when it compiles the function. If an assignment appears anywhere in the function body, reads of that name are normally treated as reads of the local binding unless the function declares global or nonlocal.

Quick answer

Find the name in the error, then search the same function for an assignment to it. The best repair is usually to pass the value into the function and return the updated value. Use global only for deliberate module-level state, and use nonlocal when a nested function must update a variable in its nearest enclosing function.

The important detail is compile-time scope analysis. A later assignment in a branch can still make the name local for the entire function, even if that branch is not taken during the failing call. The official references for UnboundLocalError, the global statement, the nonlocal statement, and Python scopes describe the rule.

UnboundLocalError scope flow showing Python local binding and pass return global and nonlocal fixes
An assignment anywhere in a function can make the name local; choose an explicit state flow or a deliberate scope declaration.

Reproduce the error

The module-level value exists, but the assignment inside the function changes how Python classifies the name.

count = 10

def broken_increment():
    print(count)
    count = count + 1
    return count

try:
    broken_increment()
except UnboundLocalError as error:
    print(type(error).__name__)
    print(error)

Python sees the assignment to count and treats count as local throughout broken_increment(). The first print(count) therefore reads an uninitialized local variable rather than the module-level value.

Python Pool infographic showing a function scope, local name, branch assignment, and variable lookup
Local scope: A function scope, local name, branch assignment, and variable lookup.

Return the updated value

The most maintainable fix is to make state flow explicit. Pass the current value in, calculate the next value, and return it to the caller.

def increment(current):
    next_count = current + 1
    return next_count

count = 10
count = increment(count)

print(count)

This design works for counters, totals, configuration values, and small transformations. It avoids hidden dependencies and makes a function easy to test with one input and one expected output.

Use a local default when state is local

If the function owns the state, initialize it before the first read. This is appropriate when each call should start from a fresh value.

def build_labels(items):
    labels = []
    for item in items:
        labels.append(str(item))
    return labels

print(build_labels([1, 2, 3]))

Initialization should match the function’s intended lifecycle. Do not add a default merely to silence the exception if the value is required from the caller. In that case, make it a parameter and validate it at the boundary.

Python Pool infographic comparing read-before-assignment, conditional branches, global names, and locals
Binding cause: Read-before-assignment, conditional branches, global names, and locals.

Use global only for deliberate module state

The global statement tells Python that an assignment targets the module-level name. It repairs the narrow example, but it also creates shared mutable state, so callers and tests must account for the change.

count = 10

def increment_global():
    global count
    count = count + 1
    return count

print(increment_global())
print(count)

Use this approach for a genuinely process-wide setting or counter, not as a default response to the error. Shared state can leak between tests, web requests, and repeated calls.

Use nonlocal in a closure

The nonlocal statement applies to a nested function. It updates a name in the nearest enclosing function scope, not the module scope.

def make_counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter())
print(counter())

This pattern is useful when a factory intentionally returns a callable with private state. If the nested function only reads the enclosing value, no nonlocal declaration is needed; it is required for assignment.

Python Pool infographic mapping initialization, default value, nonlocal, global, and refactored control flow
Fix binding: Initialization, default value, nonlocal, global, and refactored control flow.

Check conditional assignments

A branch can leave a local name uninitialized even when the function does not read a module variable. Give every path a value or return before the read.

def label_for(value):
    if value is not None:
        label = str(value)
    else:
        label = "unknown"
    return label

print(label_for(None))
print(label_for(7))

Use an explicit else, an early return, or a default initialization. The right choice depends on whether the missing case is valid data or an error that should be reported.

Inspect scope while debugging

When the name is not obvious, inspect the function’s local variables and assignments rather than changing unrelated code. The dis module can show whether Python compiled a name as a local, but a small refactor is usually clearer than relying on bytecode details.

def sample():
    value = 1
    return value

print(sample.__code__.co_varnames)
print(sample())

Use this as a diagnostic aid, not as application logic. The durable fix is to clarify ownership of the value and make the data flow visible to the reader.

Python Pool infographic testing missing branches, loops, exceptions, closures, and deterministic state
Scope checks: Missing branches, loops, exceptions, closures, and deterministic state.

Common mistakes

  • Reading a module setting and assigning to the same name without global.
  • Initializing a variable in only one conditional branch.
  • Using global when a parameter and return value would be simpler.
  • Using nonlocal outside a nested function or when no enclosing binding exists.
  • Fixing a notebook cell without clearing an old binding.

The practical rule is to decide who owns the state. Pass and return it for ordinary business logic, initialize it when the function owns it, use global for intentional module state, and use nonlocal for intentional closure state. That removes the ambiguity that produces UnboundLocalError.

Make state ownership visible in tests

A test that calls a stateful function more than once can reveal accidental global or closure state. Prefer a fresh fixture or a new object for each test when state should not leak. If shared state is intentional, provide a reset operation or inject the state so the test can control its starting value.

Static analysis tools can also flag names that are assigned and read in confusing paths, but a linter cannot choose the correct ownership model for you. Treat the warning as a prompt to simplify the function’s inputs, outputs, and scope boundaries.

Clear scope boundaries also make refactoring safer: callers can see which values enter a function and which values come back out.

For name-binding diagnostics, compare Python inspect tools with guarded conditional imports. Read python inspect and python conditional import for the related workflow.

Frequently Asked Questions

Frequently Asked Questions

Why does local variable referenced before assignment happen?

Python treats a name as local when the function assigns to it, so a read before that assignment accesses an uninitialized local binding.

What is the best fix for UnboundLocalError?

Usually pass the value into the function and return the updated value. This makes ownership and state flow explicit.

When should I use global?

Use global only when a function intentionally updates module-level state. Otherwise prefer parameters, return values, or an object.

When do I use nonlocal?

Use nonlocal inside a nested function when it must assign to a variable in the nearest enclosing function scope.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted