【问题标题】:Why can't Python increment variable in closure?为什么 Python 不能在闭包中增加变量?
【发布时间】:2014-03-24 11:25:31
【问题描述】:

在这段代码 sn-p 中,我可以从 bar 函数内部打印计数器的值

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

bar = foo()

bar()

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

UnboundLocalError: local variable 'counter' referenced before assignment

代码 sn-p 与增量语句。

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

bar = foo()

bar()

您是否仅对 Python 闭包中的外部函数中的变量具有读取权限?

【问题讨论】:

标签: python closures


【解决方案1】:

您无法在 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()

【讨论】:

  • A+ 金德尔。你能帮我理解为什么我们不把foo 变成一个小班吗?这种方法有什么好处?
  • 感谢您的回答。因此,在 Python 2 中,您可以读取但不能更改外部函数中的变量。但是在 Python 3 中,只要使用nonlocal,就可以读取和更改。
  • 谢谢@AaronHall。所以你可以在 Python 2 中改变可变变量,你可以在 Python 3 中改变不可变变量,只要你使用nonlocal
  • @Aaron Hall:你应该阅读stackoverflow.com/questions/291978/…
  • @naomik 一个类也将是一个完美的解决方案。
【解决方案2】:

为什么 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

【讨论】:

    【解决方案3】:

    由于您无法在 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()
    

    【讨论】:

    • 这个问题与不变性无关(变量是可变的int)。这是关于从外部范围更改变量。
    猜你喜欢
    • 2020-05-04
    • 2013-03-17
    • 1970-01-01
    • 2014-04-12
    • 2020-03-23
    • 1970-01-01
    • 2017-09-27
    • 2020-09-04
    • 1970-01-01
    相关资源
    最近更新 更多