为什么Python不能在闭包中增加变量?

时间:2014-02-22 20:19:09

标签: python closures

在这段代码中,我可以从条形函数

中打印计数器的值
def foo():
    counter = 1
    def bar():
        print("bar", counter)
    return bar

bar = foo()

bar()

但是当我尝试从bar函数内部递增计数器时,我得到一个UnboundLocalError错误。

UnboundLocalError: local variable 'counter' referenced before assignment

带有递增语句的代码段。

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

bar = foo()

bar()

你是否只能在Python闭包中对外部函数中的变量进行读访问?

4 个答案:

答案 0 :(得分:57)

你不能在Python 2中改变闭包变量。在Python 3中,由于你的print(),你似乎正在使用它,你可以声明它们nonlocal

def foo():

  counter = 1

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

  return bar

bar = foo()
bar()

否则,bar()内的赋值会使变量成为局部变量,并且由于您尚未为本地范围中的变量赋值,因此尝试访问该变量是错误的。

在Python 2中,我最喜欢的解决方法如下:

def foo():

  class nonlocal:
    counter = 1

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

  return bar

bar = foo()
bar()

这是有效的,因为改变一个可变对象不需要改变变量名所指的内容。在这种情况下,nonlocal是闭包变量,它永远不会被重新分配;只是它的内容被改变了。其他解决方法使用列表或词典。

或者你可以使用一个类来完成整个事情,正如@naomik在评论中所建议的那样。定义__call__()以使实例可调用。

class Foo(object):

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

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

bar = Foo()
bar()

答案 1 :(得分:9)

  

为什么Python不能在闭包中增加变量?

错误消息不言自明。我在这里提供了几个解决方案。

  • 使用函数属性(不常见,但效果很好)
  • 使用带nonlocal的封闭(理想,但仅限Python 3)
  • 在可变对象上使用闭包(Python 2的惯用语)
  • 在自定义对象上使用方法
  • 通过实施__call__
  • 直接调用该对象的实例

在函数上使用属性。

创建函数后手动设置计数器属性:

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

foo.counter = 0

现在:

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

或者您可以自动设置功能:

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

类似地:

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

这些方法很简单,但并不常见,而且如果有人在您不在场的情况下查看您的代码,则不太可能很快被他们搞砸。

您希望完成的更常见的方式取决于您的Python版本。

Python 3,使用带nonlocal

的闭包

在Python 3中,您可以声明非本地:

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

bar = foo()

它会增加

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

对于这个问题,这可能是最惯用的解决方案。太糟糕了,它仅限于Python 3。

非局部的Python 2解决方法:

您可以声明一个全局变量,然后对其进行递增,但这会使模块命名空间变得混乱。因此,避免声明全局变量的惯用解决方法是指向包含要增加的整数的可变对象,这样您就不会尝试重新分配变量名称:

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

bar = foo()

现在:

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

我认为这比仅仅为了保存增量变量而创建类的建议更优越。但要完成,让我们看看。

使用自定义对象

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

现在:

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

甚至实施__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()

现在:

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

答案 2 :(得分:0)

由于您无法在Python中修改不可变对象,因此Python 2.X中有一种解决方法:

使用列表

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

答案 3 :(得分:-1)

nonlocal是Python 3中的一个新语句,Python 2中没有相应的内容。

有用的链接Understanding UnboundLocalError in Python