Don't understand why UnboundLocalError occurs (closure)
Solution 1
Python doesn't have variable declarations, so it has to figure out the scope of variables itself. It does so by a simple rule: If there is an assignment to a variable inside a function, that variable is considered local.[1] Thus, the line
counter += 1
implicitly makes counter
local to increment()
. Trying to execute this line, though, will try to read the value of the local variable counter
before it is assigned, resulting in an UnboundLocalError
.[2]
If counter
is a global variable, the global
keyword will help. If increment()
is a local function and counter
a local variable, you can use nonlocal
in Python 3.x.
Solution 2
You need to use the global statement so that you are modifying the global variable counter, instead of a local variable:
counter = 0
def increment():
global counter
counter += 1
increment()
If the enclosing scope that counter
is defined in is not the global scope, on Python 3.x you could use the nonlocal statement. In the same situation on Python 2.x you would have no way to reassign to the nonlocal name counter
, so you would need to make counter
mutable and modify it:
counter = [0]
def increment():
counter[0] += 1
increment()
print counter[0] # prints '1'
Solution 3
To answer the question in your subject line,* yes, there are closures in Python, except they only apply inside a function, and also (in Python 2.x) they are read-only; you can't re-bind the name to a different object (though if the object is mutable, you can modify its contents). In Python 3.x, you can use the nonlocal
keyword to modify a closure variable.
def incrementer():
counter = 0
def increment():
nonlocal counter
counter += 1
return counter
return increment
increment = incrementer()
increment() # 1
increment() # 2
* The question origially asked about closures in Python.
Solution 4
The reason why your code throws an UnboundLocalError
is already well explained in other answers.
But it seems to me that you're trying to build something that works like itertools.count()
.
So try it out, and see if it suits your case:
>>> from itertools import count
>>> counter = count(0)
>>> counter
count(0)
>>> next(counter)
0
>>> counter
count(1)
>>> next(counter)
1
>>> counter
count(2)
Solution 5
Python has lexical scoping by default, which means that although an enclosed scope can access values in its enclosing scope, it cannot modify them (unless they're declared global with the global
keyword).
A closure binds values in the enclosing environment to names in the local environment. The local environment can then use the bound value, and even reassign that name to something else, but it can't modify the binding in the enclosing environment.
In your case you are trying to treat counter
as a local variable rather than a bound value. Note that this code, which binds the value of x
assigned in the enclosing environment, works fine:
>>> x = 1
>>> def f():
>>> return x
>>> f()
1
Randomblue
Updated on March 09, 2020Comments
-
Randomblue about 4 years
What am I doing wrong here?
counter = 0 def increment(): counter += 1 increment()
The above code throws an
UnboundLocalError
.-
Zero Piraeus over 7 yearsThis question and the one it's currently marked duplicate of are under discussion in the Python chatroom.
-
PM 2Ring over 7 yearsMany of the answers here say to use
global
, and although that works, using modifiable globals is generally not recommend when other options exist. -
dsh about 7 years@ZeroPiraeus A question asked in 2012 can't be a duplicate of a question asked in 2016 ... rather the newer one is the duplicate.
-
Zero Piraeus about 7 years@dsh That's not true.
-
Masklinn about 4 years@juanpa.arrivillaga it is though the general issue is closing over and updating a binding which is not local. UnboundLocalError can also occur for fully local variables but they're a different issue (with a different solution).
-
juanpa.arrivillaga about 4 years@Masklinn yes, but a closure is not created in this case. You can check,
print(increment.__closure__)
-
Masklinn about 4 years@juanpa.arrivillaga that's an implementation detail. It's conceptually a closure, even if Python differentiates globals and non-global nonlocals. The question and primary answer is relevant to the issue either way.
-
juanpa.arrivillaga about 4 years@Masklinn OK, you've convinced me. I guess in the broadest sense a free variable is any non-local variable. Feel free to rollback
-
-
here about 10 years
-
mouckatron over 6 yearsA note that caught me out, I had a variable declared at the top of the file that I can read inside a function without issue, however to write to a variable that I have declared at the top of the file, I had to use global.
-
Yibo Yang almost 6 yearsA more in-depth explanation: docs.python.org/3.3/reference/…. Not only can assignments bind names, so can imports, so you may also get
UnboundLocalError
from a statement that uses an unbounded imported name. Example:def foo(): bar = deepcopy({'a':1}); from copy import deepcopy; return bar
, thenfrom copy import deepcopy; foo()
. The call succeeds if the local importfrom copy import deepcopy
is removed.