How can a name be "unbound" in Python? What code can cause an `UnboundLocalError`?

Question:

I read the following in the Python documentation:

When a name is not found at all, a NameError exception is raised. If the current scope is a function scope, and the name refers to a local variable that has not yet been bound to a value at the point where the name is used, an UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.

Python lacks declarations and allows name binding operations to occur anywhere within a code block.

I don’t understand how this works. If there are no declarations, then when does UnboundLocalError get raised? How can the variable "not yet be bound" when it is encountered?


See also UnboundLocalError trying to use a variable (supposed to be global) that is (re)assigned (even after first use) for a common problem where a variable expected to be global, is local instead. This question is focused on cases where the programmer expects the variable to be local.

Asked By: user2889159

||

Answers:

You can refer to a name without having assigned to it:

>>> foobar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foobar' is not defined

Here foobar is being referred to, but was never assigned to. This raises a NameError because the name was never bound.

More subtly, here assignment is not happening because the line that does is never run:

>>> def foo():
...     if False:
...         spam = 'eggs'
...     print(spam)
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in foo
UnboundLocalError: local variable 'spam' referenced before assignment

Because spam = 'eggs' is never executed, print(spam) raises an UnboundLocalError.

Note that nowhere in Python is a name ever declared. You bind or don’t bind, declaration is not part of the language.

Instead, binding is used to determine the scope of a name; binding operations include assignment, names used for a for loop, function parameters, import statements, name to hold a caught exception in an except clause, and the name for a context manager in a with statement.

If a name is bound in a scope (such as in a function) then it is a local name, unless you use a global statement (or a nonlocal statement in Python 3) to explicitly mark the name as a global (or a closure) instead.

So the following is an error:

>>> foo = None
>>> def bar():
...     if False:
...         foo = 'spam'
...     print(foo)
... 
>>> bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in bar
UnboundLocalError: local variable 'foo' referenced before assignment

because foo is being bound somewhere in the bar function scope. But if you mark foo as a global, the function works:

>>> foo = None
>>> def bar():
...     global foo
...     if False:
...         foo = 'spam'
...     print(foo)
... 
>>> bar()
None

because now the Python compiler knows you wanted foo to be a global instead.

This is all documented in the Naming and Binding section of the Python reference documentation.

Answered By: Martijn Pieters

One more place is when the variable is declared in global scope and if it is used in function scope, it should specifically refer as global otherwise UnboundLocalError occurs. Following is the example:

var = 123
def myfunc():
    var = var + 1
    print var

myfunc()

Error:

UnboundLocalError: local variable 'var' referenced before assignment
Answered By: venpa

UnboundLocalError is a special kind of NameError, that specifically refers to local variables. The same kinds of logical problems that cause NameErrors in code outside of functions, can cause UnboundLocalError inside them.

Commonly, the problem occurs when trying to write conditional logic that doesn’t have any fallback:

import random

def example_if():
    if random.random() < 0.5:
        result = 1
    return result

print(example_if())

This will fail whenever the condition isn’t satisfied, because in that case there wasn’t anything assigned to result, so there’s no way to use it. It’s important to understand here that there are no "default" values for variables, and that assigning None to something is not the same as deleting the name with del.

(This could also be a symptom of a logical error with indentation: check whether the code using the variable is also supposed to be inside the if block.)

A sneaky variation on this is a loop that is supposed to assign the value, but runs zero times:

def example_for():
    for i in range(0): # this loop will not be entered
        result = 1 # therefore this assignment will not happen
    return result

print(example_for()))

In more practical examples, a for loop might fail to run because it is iterating over results from a searching operation (e.g. another loop) that didn’t find anything, or because it is iterating over a file that was already read.

UnboundLocalError can also occur when trying to modify a variable that wasn’t already assigned:

def example_increment():
    result += 1

example_increment()

This happens because the existing value needs to be looked up before it can be modified, but there is nothing to look up.

Either way, the error will happen even if there is a var in the global scope, because Python decided ahead of time to treat the variable as local. To solve this, use the global keyword in addition to giving the initial global value. It must be used in each function that wants to modify the global. (global in global scope has no effect.)

import random

result = 1

def example_increment():
    global result
    result += 1

def example_if():
    global result
    if random.random() < 0.5:
        result = 1
    return result

example_increment() # result becomes 2
print(example_if()) # may or may not reset to 1

Using global is not necessary if the value is read-only. However, a global cannot be used to set an initial value for a local of the same name:

result = 1

def does_not_work():
    result = result # trying to set a "default" value for a local
    if random.random() < 0.5:
        result = 2
    return result

This can’t work either way. As is, UnboundLocalError will occur. If global result is added to the function, then result = result will have no effect, and the function will potentially modify the global (not wanted here). For these cases, the workaround is to simply use a different name for the local.

Answered By: Karl Knechtel
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.