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

3

15

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.

Street answered 28/2, 2014 at 17:25 Comment(1)
For duplicate closers: Please do not use stackoverflow.com/questions/15367760 to close questions where OP had a logic error preventing a local from being assigned in some situations. Use this one instead (or if you find something better, please show me in chat.stackoverflow.com/rooms/247434/python-canon-discussion). The other one has a poor setup; understanding the problem is complicated by the issue of trying to read a file twice (hence me closing it with two very different duplicates).Revisionism
M
26

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.

Myocarditis answered 28/2, 2014 at 17:27 Comment(9)
Very nice example, thanks! Does it mean that a specified block bindings are created before the code of this block is starting to execute?Street
@Dmitrii: the compiler stores local names with the code block; this so the byte code can use the correct opcodes to load global names vs. local names vs. closures.Myocarditis
Python decides whether a variable is local while compiling to byte code. If it's ever assigned to, it's local (unless there is a global or nonlocal declaration for it).Vaucluse
@kindall: not just assignment; importing, function parameters, for loop targets, except and with clause as targets all bind names.Myocarditis
You are technically correct... the best kind of correct. (In my mind all those are assignments, though.)Vaucluse
@kindall: The reference manual calls it binding, then names assignment as an action that binds. Best to stick to the official terminology when writing answers on SO. :-)Myocarditis
@Martijn Pieters Thanks for your answer, but I don't understand you. Where can I read about python bytecode instruction and how to compile sources to bytecode?Street
"This is all documented in the Naming and Binding section of the Python reference documentation." Yes, but... that's where OP was quoting from.Revisionism
@KarlKnechtel: Not exactly. They quoted from the 'Resolution of names', but they should also read the important 'Binding of names' section before it.Myocarditis
C
4

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
Choppy answered 28/2, 2014 at 17:32 Comment(1)
I had edited this, but then rolled it back because I think I changed the spirit of it too much. Instead, I integrated the changes I wanted to make into a completely new answer.Revisionism
R
2

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. You would need to explicitly define a fallback, for example:

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

(This error 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 result 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.

Revisionism answered 6/2, 2023 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.