188

What am I doing wrong here?

counter = 0

def increment():
  counter += 1

increment()

The above code throws an UnboundLocalError.

juanpa.arrivillaga
  • 65,257
  • 7
  • 88
  • 122
Randomblue
  • 98,379
  • 133
  • 328
  • 526
  • 1
    This question and the one it's currently marked duplicate of are under discussion in the [Python chatroom](http://chat.stackoverflow.com/transcript/message/34899645#34899645). – Zero Piraeus Jan 03 '17 at 01:30
  • 5
    Many of the answers here say to use `global`, and although that works, using modifiable globals is generally _not_ recommend when other options exist. – PM 2Ring Jan 03 '17 at 01:34
  • 14
    @ZeroPiraeus A question asked in 2012 can't be a duplicate of a question asked in 2016 ... rather the newer one is the duplicate. – dsh Feb 14 '17 at 17:04
  • 9
    @dsh [That's not true](http://meta.stackexchange.com/a/147651). – Zero Piraeus Feb 14 '17 at 18:03
  • @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). – Masklinn Mar 09 '20 at 09:27
  • @Masklinn yes, but a *closure is not created in this case*. You can check, `print(increment.__closure__)` – juanpa.arrivillaga Mar 09 '20 at 09:28
  • @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. – Masklinn Mar 09 '20 at 09:29
  • @Masklinn OK, you've convinced me. I guess in the broadest sense a free variable is any non-local variable. Feel free to rollback – juanpa.arrivillaga Mar 09 '20 at 09:31

8 Answers8

184

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.

Honest Abe
  • 7,434
  • 4
  • 45
  • 59
Sven Marnach
  • 483,142
  • 107
  • 864
  • 776
  • 11
    python 3 docs has a [faq page on why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value](https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value) via [unboundlocalerror-local-variable-l-referenced-before-assignment-python](http://stackoverflow.com/questions/21456739/unboundlocalerror-local-variable-l-referenced-before-assignment-python) – here May 04 '14 at 07:19
  • 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. – mouckatron Feb 06 '18 at 14:35
  • A more in-depth explanation: https://docs.python.org/3.3/reference/executionmodel.html#naming-and-binding. 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. – Yibo Yang Jun 27 '18 at 16:00
89

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'
Andrew Clark
  • 180,281
  • 26
  • 249
  • 286
27

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.

kindall
  • 158,047
  • 31
  • 244
  • 289
8

The reason of 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 why don't you 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)
Rik Poggi
  • 25,244
  • 6
  • 61
  • 80
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
Honest Abe
  • 7,434
  • 4
  • 45
  • 59
Chris Taylor
  • 44,831
  • 12
  • 101
  • 146
3

To modify a global variable inside a function, you must use the global keyword.

When you try to do this without the line

global counter

inside of the definition of increment, a local variable named counter is created so as to keep you from mucking up the counter variable that the whole program may depend on.

Note that you only need to use global when you are modifying the variable; you could read counter from within increment without the need for the global statement.

chucksmash
  • 4,955
  • 1
  • 27
  • 40
1

try this

counter = 0

def increment():
  global counter
  counter += 1

increment()
Lostsoul
  • 21,503
  • 39
  • 122
  • 205
-1

Python is not purely lexically scoped.

See this: Using global variables in a function

and this: https://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/

Marcin
  • 44,601
  • 17
  • 110
  • 191
  • 3
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/19036082) – munk Mar 07 '18 at 19:05