带闭包的python计数器

时间:2016-08-01 07:32:27

标签: python closures

我正在尝试使用closure属性在python中构建一个计数器。以下代码中的代码:

def generate_counter():
    CNT = [0]
    def add_one():
        CNT[0] = CNT[0] + 1
        return CNT[0]
    return add_one

但是,当我将列表CNT更改为var时,它不起作用:

def generate_counter1():
    x = 0
    def add_one():
        x = x + 1
        return x
    return add_one

当我打印实例的closure属性时,我发现第二种情况的__closure__是none:

>>> ct1 = generate_counter()
>>> ct2 = generate_counter1()
>>> print(ct1.__closure__[0])
<cell at 0xb723765c: list object at 0xb724370c>
>>> print(ct2.__closure__)
None

只是想知道为什么外部函数中的索引必须是一个列表?

感谢您的回答!找到了明确解释这个问题的文档 https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2 个答案:

答案 0 :(得分:4)

Python通过查看名称绑定行为来确定名称的范围;赋值是一种这样的行为(函数参数,导入,for target ...while .. as target中的目标是其他示例)。您在函数中绑定的名称被视为 local 。请参阅参考文档的Naming and Binding section

因此,第二个示例中的名称x local 变量,因为您直接指定了该变量:

x = x + 1

事实上,因为您从未向x提供本地值,所以当您尝试使用该函数时,您将获得异常;当您尝试阅读本地名称​​ unbound 时:

>>> 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

在你的第一个例子中没有发生这种绑定;您正在改变CNT内容,该名称引用的内容不会被更改。

如果您使用的是Python 3,则可以使用nonlocal statement覆盖使名称成为本地名称的决定:

def generate_counter2():
    x = 0
    def add_one():
        nonlocal x
        x = x + 1
        return x
    return add_one

通过使x非本地,Python在父上下文中找到它并再次为它创建一个闭包。

>>> 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是Python 3中的新功能;在Python 2中,您仅限于使用可变列表对象来规避绑定规则的技巧。另一个技巧是将计数器分配给嵌套函数的属性;再次,这避免了绑定当前范围中的名称:

def generate_counter3():
    def add_one():
        add_one.x += 1
        return add_one.x
    add_one.x = 0
    return add_one

答案 1 :(得分:0)

它没有 成为一个列表,它只需要是一个可变对象,你 mutate ,而不是重新分配。

来自docs

  

如果在函数体内的任何位置为变量赋值,则除非明确声明为全局,否则将其视为局部值。

因此,在您的第二个示例中,x被视为本地(对于内部函数),并且在您第一次分配后,您将对外部&#39; <阴影进行遮蔽。< / p>

另一方面,在第一个示例中(因为您没有为CNT分配值),它在外部函数中定义的CNT上运行。