Why can't Python increment variable in closure?

The error message is self-explanatory. 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

首页 - Wiki
Copyright © 2011-2024 iteam. Current version is 2.125.0. UTC+08:00, 2024-05-05 21:05
浙ICP备14020137号-1 $访客地图$