Don't understand why UnboundLocalError occurs (closure)

252,665

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
Share:
252,665
Randomblue
Author by

Randomblue

Updated on March 09, 2020

Comments

  • Randomblue
    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
      Zero Piraeus over 7 years
      This question and the one it's currently marked duplicate of are under discussion in the Python chatroom.
    • PM 2Ring
      PM 2Ring over 7 years
      Many of the answers here say to use global, and although that works, using modifiable globals is generally not recommend when other options exist.
    • dsh
      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
      Zero Piraeus about 7 years
    • Masklinn
      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
      juanpa.arrivillaga about 4 years
      @Masklinn yes, but a closure is not created in this case. You can check, print(increment.__closure__)
    • Masklinn
      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
      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
    here about 10 years
  • mouckatron
    mouckatron over 6 years
    A 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
    Yibo Yang almost 6 years
    A 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, then from copy import deepcopy; foo(). The call succeeds if the local import from copy import deepcopy is removed.