关于python:递归闭包中的作用域错误

scoping error in recursive closure

为什么这样做:

1
2
3
4
5
def function1():                                                                                                            
       a = 10                                                                                                                    
       def function2():
          print a
       function2()

但这并不是:

1
2
3
4
5
6
7
8
def function1():
    a = 10
    def function2():
        print a
        a -= 1
        if a>0:
           function2()
    function2()

我得到这个错误:

1
UnboundLocalError: local variable 'a' referenced before assignment

这个错误似乎并不能很好地描述根本问题。Mike解释了这些信息,但这并不能解释根本原因。

实际的问题是,在python中,不能给封闭变量赋值。因此在函数2中,"a"是只读的。当你给它赋值时,你会创建一个新的变量,正如迈克指出的那样,你在写之前就已经读过了。

如果要从内部范围将赋值给外部变量,则必须这样做:

1
2
3
4
5
6
7
8
def function1():
    al = [10]
    def function2():
        print al[0]
        al[0] -= 1
        if al[0]>0:
           function2()
    function2()

因此,al是不变的,但它的内容不是,您可以在不创建新变量的情况下更改它们。


应该注意,这是Python中的语法错误。python本身(在字节码级别)可以很好地分配给这些变量;2.x中没有语法来表示您想要这样做。它假定,如果在嵌套级别中为变量赋值,则意味着它是局部变量。

这是一个巨大的缺点;能够分配给闭包是最基本的。我已经和查理的黑客解决过几次了。

python 3用非常笨拙的"nonlocal"关键字修复了这个问题:

1
2
3
4
5
6
7
8
9
10
def function1():
    a = 10
    def function2():
        nonlocal a
        print(a)
        a -= 1
        if a>0:
           function2()
    function2()
function1()

这种语法只在3.x中可用是非常糟糕的;大多数人都困在2.x中,必须继续使用黑客来解决这个问题。这非常需要反向移植到2.x。

http://www.python.org/dev/peps/pep-3104/


在非工作代码段中,当您说"a -= 1"时,您将分配给a。因此,在function2中,a是该范围的局部,而不是封闭范围。python的闭包是词法上的,它不会动态地在function2的框架中查找a,如果没有分配,请在function1的框架中查找它。

注意,这完全不依赖于递归性或使用闭包。考虑这个函数的例子

1
2
3
def foo():
    print a
    a = 4

打电话给它也会给你一个UnboundLocalError。(如果没有a = 4,它将使用全球a,或者如果没有,则提高NameError。因为a可能在该范围内分配,所以它是本地的。

如果我在设计这个函数,我可能会使用一种更像

1
2
3
4
5
6
7
8
def function1():
    a = 10
    def function2(a=a):
        print a
        a -= 1
        if a > 0:
           function2(a)
    function2()

(当然也可以是for a in xrange(10, -1, -1): print a—)