递归闭包中的作用域错误
为什么这个可以运行:
def function1():
a = 10
def function2():
print a
function2()
但这个却不行:
def function1():
a = 10
def function2():
print a
a -= 1
if a>0:
function2()
function2()
我遇到了这个错误:
UnboundLocalError: local variable 'a' referenced before assignment
3 个回答
在这个不工作的代码片段中,当你写到"a -= 1
"时,你实际上是在给赋值。因为这样,function2
里的a
就只在这个函数内部有效,而不是从外面的范围拿。Python的闭包是基于词法的,也就是说,它不会动态地在function2
的环境中查找a
,如果没有找到,就不会去function1
里找。
需要注意的是,这个问题和递归或闭包没有关系。想象一下这个函数的例子:
def foo():
print a
a = 4
调用这个函数也会出现UnboundLocalError
错误。(如果没有a = 4
,它要么会使用全局的a
,要么如果没有全局的a
,就会抛出NameError
错误。)因为a
在这个范围内可能被赋值,所以它被视为局部变量。
如果我是设计这个函数的人,我可能会采用更像下面这样的方式:
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
来实现;-))
需要注意的是,这在Python中是一个语法问题。Python本身(在字节码层面)可以正常给这些变量赋值;只是2.x版本没有语法来表示你想这么做。它假设如果你在一个嵌套的层级中给变量赋值,你的意思是这个变量是局部的。
这真是个大缺陷;能够给闭包(即函数内部的函数)赋值是非常重要的。我已经用charlieb的技巧多次解决了这个问题。
Python 3通过一个名字很奇怪的“nonlocal”关键字修复了这个问题:
def function1():
a = 10
def function2():
nonlocal a
print(a)
a -= 1
if a>0:
function2()
function2()
function1()
很遗憾,这个语法只在3.x版本中可用;大多数人还在用2.x版本,只能继续使用一些技巧来绕过这个问题。这个功能非常需要被移植到2.x版本。
这个错误信息看起来并没有清楚地说明问题的根本原因。Mike 解释了这些信息,但这并没有解释真正的问题所在。
实际的问题是,在 Python 中,你不能对被封闭的变量进行赋值。所以在 function2 中,'a' 是只读的。当你试图给它赋值时,其实是创建了一个新的变量,正如 Mike 所指出的,你是在写之前先读取了它。
如果你想从内部作用域给外部变量赋值,你需要用一些小技巧,像这样:
def function1():
al = [10]
def function2():
print al[0]
al[0] -= 1
if al[0]>0:
function2()
function2()
所以 al 是不可变的,但它的内容是可以变的,你可以在不创建新变量的情况下改变它们。