53

In this code snippet I can print the value of counter from inside the bar function

def foo():
    counter = 1
    def bar():
        print("bar", counter)
    return bar

bar = foo()

bar()

But when I try to increment counter from inside the bar function I get an UnboundLocalError error.

UnboundLocalError: local variable 'counter' referenced before assignment

Code snippet with increment statement in.

def foo():
    counter = 1
    def bar():
        counter += 1
        print("bar", counter)
    return bar

bar = foo()

bar()

Do you only have read access to variables in the outer function in a Python closure?

Neil
  • 8,925
  • 10
  • 44
  • 49
  • 2
    This is a duplicate to http://stackoverflow.com/questions/2009402/read-write-python-closures?rq=1 – hivert Feb 22 '14 at 20:23

3 Answers3

91

You can't re-bind closure variables in Python 2. In Python 3, which you appear to be using due to your print(), you can declare them nonlocal:

def foo():

  counter = 1

  def bar():
    nonlocal counter
    counter += 1
    print("bar", counter)

  return bar

bar = foo()
bar()

Otherwise, the assignment within bar() makes the variable local, and since you haven't assigned a value to the variable in the local scope, trying to access it is an error.

In Python 2, my favorite workaround looks like this:

def foo():

  class nonlocal:
    counter = 1

  def bar():
    nonlocal.counter += 1
    print("bar", nonlocal.counter)

  return bar

bar = foo()
bar()

This works because mutating a mutable object doesn't require changing what the variable name points to. In this case, nonlocal is the closure variable and it is never reassigned; only its contents are changed. Other workarounds use lists or dictionaries.

Or you could use a class for the whole thing, as @naomik suggests in a comment. Define __call__() to make the instance callable.

class Foo(object):

    def __init__(self, counter=1):
       self.counter = counter

    def __call__(self):
       self.counter += 1
       print("bar", self.counter)

bar = Foo()
bar()

For completeness, I'll suggest that you could also write the function as a generator using the last gen2func decorator in this answer of mine. This lets you use regular local variables to maintain state across calls, because the function isn't really a function and never actually exits. However, this is more a "ain't that cool" than a recommended solution!

kindall
  • 178,883
  • 35
  • 278
  • 309
  • A+ Kindall. Can you help me understand why we wouldn't just turn `foo` into a tiny class though? What's the advantage of this approach? – Mulan Feb 22 '14 at 20:27
  • Thanks for your answer. So in Python 2 you can read but can't change variables in the outer function. But in Python 3 you can read and change as long as you use `nonlocal`. – Neil Feb 22 '14 at 20:30
  • Thanks @AaronHall. So you can change mutable variables in Python 2 and you can change immutable variables in Python 3 as long as you use `nonlocal` – Neil Feb 22 '14 at 20:37
  • @Aaron Hall: You should read http://stackoverflow.com/questions/291978/short-description-of-python-scoping-rules – hivert Feb 22 '14 at 20:50
  • 1
    @naomik A class would be a perfectly fine solution to this too. – kindall Feb 22 '14 at 21:03
  • @kindall Very cool, didn't know either of those tricks about Python 2 or 3! – Andy Feb 25 '17 at 00:01
  • @naomik the disadvantage of turning `foo` into a tiny class is that you would have to call the function on the instance instead of by itself: `c = foo() \ c.bar() \ c.bar() \ c.bar()` as opposed to just `bar = foo() \ bar() \ bar() \ bar()` – Andy Feb 25 '17 at 00:07
  • That's what `__call__()` is for – kindall Jun 12 '20 at 20:22
15

Why can't Python increment variable in closure?

I offer a couple of solutions here.

  • Using a function attribute (uncommon, but works pretty well)
  • Using a closure with nonlocal (ideal, but Python 3 only)
  • Using a closure over a mutable object (idiomatic of Python 2)
  • Using a method on a custom object
  • Directly calling instance of the object by implementing __call__

Use an attribute on the function.

Set a counter attribute on your function manually after creating it:

def foo():
    foo.counter += 1
    return foo.counter
    
foo.counter = 0

And now:

>>> foo()
1
>>> foo()
2
>>> foo()
3

Or you can auto-set the function:

def foo():
    if not hasattr(foo, 'counter'):
        foo.counter = 0
    foo.counter += 1
    return foo.counter

Similarly:

>>> foo()
1
>>> foo()
2
>>> foo()
3

These approaches are simple, but uncommon, and unlikely to be quickly grokked by someone viewing your code without you present.

More common ways what you wish to accomplish is done varies depending on your version of Python.

Python 3, using a closure with nonlocal

In Python 3, you can declare nonlocal:

def foo():
    counter = 0
    def bar():
        nonlocal counter
        counter += 1
        print("bar", counter)
    return bar

bar = foo()

And it would increment

>>> bar()
bar 1
>>> bar()
bar 2
>>> bar()
bar 3

This is probably the most idiomatic solution for this problem. Too bad it's restricted to Python 3.

Python 2 workaround for nonlocal:

You could declare a global variable, and then increment on it, but that clutters the module namespace. So the idiomatic workaround to avoid declaring a global variable is to point to a mutable object that contains the integer on which you wish to increment, so that you're not attempting to reassign the variable name:

def foo():
    counter = [0]
    def bar():
        counter[0] += 1
        print("bar", counter)
    return bar

bar = foo()

and now:

>>> bar()
('bar', [1])
>>> bar()
('bar', [2])
>>> bar()
('bar', [3])

I do think that is superior to the suggestions that involve creating classes just to hold your incrementing variable. But to be complete, let's see that.

Using a custom object

class Foo(object):
    def __init__(self):
        self._foo_call_count = 0
    def foo(self):
        self._foo_call_count += 1
        print('Foo.foo', self._foo_call_count)

foo = Foo()

and now:

>>> foo.foo()
Foo.foo 1
>>> foo.foo()
Foo.foo 2
>>> foo.foo()
Foo.foo 3

or even implement __call__:

class Foo2(object):
    def __init__(self):
        self._foo_call_count = 0
    def __call__(self):
        self._foo_call_count += 1
        print('Foo', self._foo_call_count)

foo = Foo2()

and now:

>>> foo()
Foo 1
>>> foo()
Foo 2
>>> foo()
Foo 3
vaer-k
  • 10,923
  • 11
  • 42
  • 59
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
0

As you can't modify immutable objects in Python, there is a workaround in Python 2.X:

Use List

def counter(start):
    count = [start]
    def incr():
        count[0] += 1
        return count[0]
    return incr

a = counter(100)
print a()
b = counter(200)
print b()
jrbedard
  • 3,662
  • 5
  • 30
  • 34
Anurag Ratna
  • 311
  • 4
  • 11
  • The question has nothing to do with immutability (the variable is a mutable `int`). It's about changing a variable from an outer scope. – MEMark Dec 22 '20 at 09:04