Python: Modifying a Counter in a Closure
I helped a fellow Pythonista with a problem today, and it reminded me that a lot of people might not know about the trick of using a list to box variables within a closure. If that doesn't make sense, read the code below:
def counter(start):
"""Create a counter starting at ``start``."""
# The value "curr" needs to be wrapped in a list. Otherwise, when
# "+=" is used in "inc", Python binds the variable at that scope
# instead of at this scope. We don't want to redefine a variable at
# the inner scope. We want to modify the variable at this outer
# scope. Java programmers would call this "boxing", but they would
# use an Integer instance instead of a list.
curr = [start]
def inc():
"""Increment the counter and return the new value."""
curr[0] += 1
return curr[0]
return inc
if __name__ == '__main__':
c = counter(5)
for i in range(3):
print c() # Prints 6 7 8.
Comments
Now your code will be understandable even to the uninitiated. :)
count[0] += 1
return count[0]
def counter(start, stop):
while start < stop:
yield start
start += 1
for i in counter(5,10):
print i
# prints 5 6 7 8 9
or even
for i in [i + 5 for i in range(3)]:
print i
# prints 5 6 7
?
"inc.count" is an attribute on the function object, not a variable inside the function.
class Foo:
def __init__(self):
private_vars = [ 0, "Alex" ]
def count(): return private_vars[0]
self.count = count
def set_count(c): private_vars[0] = c
self.set_count = set_count
def name(): return private_vars[1]
self.name = name
def set_name(n): private_vars[1] = n
self.set_name = set_name
Now you can do this:
f = Foo()
f.set_name("someone else...")
> count[0] += 1
> return count[0]
Unfortunately, this provides only a single global counter, which is insufficient. The other versions create a new counter for each closure.
Mutable static defaults--ugly ;)
Functions are objects. With the exception of some immutable objects like strings, you can attach stuff to any object.
Sorry, you missed my point. You are absolutely right that generators are better mechanisms to create counters. I wasn't really trying to create a counter. I was trying to show how to modify a lexically scoped, non-local variable using the list trick.
Nice! I hadn't seen that :)
I understand that I can attach attributes to any function. What I don't understand is that the function object is available in the function body when the function evaluation hasn't been completed yet (in the function body).
Oh, now I see your confusion. You are correct that you cannot attach function attributes (that I know of) before the function definition is done. PJE was saying you can attach attributes to the inner function from the outer function.
>>> def inc():
... inc.count += 1
... return inc.count
...
>>> inc.count = 0
>>> inc()
1
>>> inc()
2
Hmm, that makes sense, actually. By the time the function body is executed, the inc() function is fully defined in the global namespace, so now the function body can access the link to the function object in the global scope.
Ah, yes, you are correct. Very good. It's not really "global" of course, but I think you've nailed it.