I'm trying to build a counter in python with the property of closure. The code in the following works:
def generate_counter():
CNT = [0]
def add_one():
CNT[0] = CNT[0] + 1
return CNT[0]
return add_one
However when I change the list CNT to a var, it did not work:
def generate_counter1():
x = 0
def add_one():
x = x + 1
return x
return add_one
when I print the closure property of an instance, I found the __closure__
of the second case is none:
>>> ct1 = generate_counter()
>>> ct2 = generate_counter1()
>>> print(ct1.__closure__[0])
<cell at 0xb723765c: list object at 0xb724370c>
>>> print(ct2.__closure__)
None
Just wondering why the index in outer function has to be a list?
Thanks for the answers! Found the docs which clearly explained this problem https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
Python determines the scope of a name by looking at name binding behaviour; assignment is one such behaviour (function parameters, importing, the target in for target ...
or while .. as target
are other examples). A name you bind to in a function is considered local. See the Naming and Binding section of the reference documentation.
So the name x
in your second example is a local variable, because you assigned directly to it:
x = x + 1
In fact, because you never gave that x
a local value, you'll get an exception when you try to use that function; the local name is unbound when you try to read it:
>>> generate_counter1()()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in add_one
UnboundLocalError: local variable 'x' referenced before assignment
In your first example no such binding takes place; you are instead altering the contents of CNT
, what that name references is not altered.
If you are using Python 3, you can override the decision to make a name local, by using a nonlocal
statement:
def generate_counter2():
x = 0
def add_one():
nonlocal x
x = x + 1
return x
return add_one
By making x
non-local, Python finds it in the parent context and creates a closure for it again.
>>> def generate_counter2():
... x = 0
... def add_one():
... nonlocal x
... x = x + 1
... return x
... return add_one
...
>>> generate_counter2().__closure__
(<cell at 0x1078c62e8: int object at 0x1072c8070>,)
nonlocal
is new in Python 3; in Python 2 you are limited to tricks like using a mutable list object to evade the binding rule. Another trick would be to assign the counter to an attribute of the nested function; again, this avoids binding a name in the current scope:
def generate_counter3():
def add_one():
add_one.x += 1
return add_one.x
add_one.x = 0
return add_one
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With