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

Nei*_*eil 38 python closures

在这段代码中,我可以从条形函数内部打印计数器的值

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

bar = foo()

bar()
Run Code Online (Sandbox Code Playgroud)

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

UnboundLocalError: local variable 'counter' referenced before assignment
Run Code Online (Sandbox Code Playgroud)

带有增量语句的代码片段.

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

bar = foo()

bar()
Run Code Online (Sandbox Code Playgroud)

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

kin*_*all 64

你不能在Python 2中改变闭包变量.在你看来使用的Python 3中print(),你可以声明它们nonlocal:

def foo():

  counter = 1

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

  return bar

bar = foo()
bar()
Run Code Online (Sandbox Code Playgroud)

否则,内部赋值bar()使变量为local,并且由于您尚未为本地范围中的变量赋值,因此尝试访问它是一个错误.

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

def foo():

  class nonlocal:
    counter = 1

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

  return bar

bar = foo()
bar()
Run Code Online (Sandbox Code Playgroud)

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

或者你可以使用一个类来完成整个事情,正如@naomik在评论中所建议的那样.


Aar*_*all 9

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

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

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

在函数上使用属性.

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

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

foo.counter = 0
Run Code Online (Sandbox Code Playgroud)

现在:

>>> foo()
1
>>> foo()
2
>>> foo()
3
Run Code Online (Sandbox Code Playgroud)

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

def foo():
    if not hasattr(foo, 'counter'):
        foo.counter = 0
    foo.counter += 1
    return foo.counter
Run Code Online (Sandbox Code Playgroud)

同理:

>>> foo()
1
>>> foo()
2
>>> foo()
3
Run Code Online (Sandbox Code Playgroud)

这些方法很简单,但不常见,并且不会在没有您出席的情况下被查看您的代码的人迅速搞砸.

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

Python 3,使用闭包 nonlocal

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

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

bar = foo()
Run Code Online (Sandbox Code Playgroud)

它会增加

>>> bar()
bar 1
>>> bar()
bar 2
>>> bar()
bar 3
Run Code Online (Sandbox Code Playgroud)

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

非局部的Python 2解决方法:

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

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

bar = foo()
Run Code Online (Sandbox Code Playgroud)

现在:

>>> bar()
('bar', [1])
>>> bar()
('bar', [2])
>>> bar()
('bar', [3])
Run Code Online (Sandbox Code Playgroud)

我认为这比涉及创建类只是为了保存递增变量的建议更优越.但要完成,让我们看看.

使用自定义对象

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()
Run Code Online (Sandbox Code Playgroud)

现在:

>>> foo.foo()
Foo.foo 1
>>> foo.foo()
Foo.foo 2
>>> foo.foo()
Foo.foo 3
Run Code Online (Sandbox Code Playgroud)

甚至实施__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()
Run Code Online (Sandbox Code Playgroud)

现在:

>>> foo()
Foo 1
>>> foo()
Foo 2
>>> foo()
Foo 3
Run Code Online (Sandbox Code Playgroud)