Python自由变量。这为什么会失败?
下面的代码会输出123:
>>> a = 123
>>> def f():
... print a
...
>>> f()
123
>>>
但是下面的代码却失败了:
>>> a = 123
>>> def f():
... print a
... a = 456
... print a
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment
>>>
我本来以为这段代码会输出:
123
456
我这里漏掉了什么呢?
附注:我使用的是Python 2.6.6,如果这有影响的话。
4 个回答
你所使用的东西叫做闭包:就是从外部范围拿一个变量,然后把它放在一个函数块里。
你的代码是完全没问题的,在JavaScript中可以正常工作。
可惜的是,在Python中,闭包是只读的。
而且你会遇到的错误信息总是UnboundLocalError: local variable 'var_name' referenced before assignment
,这个错误信息其实很误导。
简单来说,这不是你的问题,而是语言的限制加上一个糟糕的错误提示。
编辑:
我看到这里有好几个人建议使用global
,但这会有危险的副作用:你可能会访问到在当前作用域上方的多个作用域中同名的变量,这可不是你想要的闭包效果。
在Python 3中,解决方案是引入了nonlocal
关键字,它正是用来在内部作用域中重新绑定外部作用域的变量。
对于Python 2.x,有一种方法可以模拟nonlocal,但其实你最好不要给你的变量赋值:只需复制值、返回值,或者只修改可变类型的值,这样就没问题了。
这是因为在Python中,除非你在函数里定义或尝试修改一个变量,否则它会自动认为这个变量是全局的。你可以试着在你的代码里加上global a
。
>>> a = 123
>>> def f():
... global a
... print a
... a = 456
... print a
...
>>> f()
123
456
>>> a
456
在第一个例子中,你没有定义也没有修改这个变量,所以它就是全局变量。但是如果你想给a加20,比如说,你也需要使用global a
。
另外要注意,函数f中的a是全局变量,运行完f函数后它的值会发生变化。
如果你想创建一个局部变量,记得声明总是要在使用之前,所以print a
不能在a = 456
之前执行。
编辑: 好吧,既然我们在讨论闭包和使用全局变量的危险性,还有其他的可能性。
>>> a = 123
>>> def f():
... b = a
... print b
... b = 456
... print b
...
>>> f()
123
456
>>> a
123
>>>
在这里,我们利用闭包的只读特性来复制a,然后修改这个复制的值,而不改变外面的a
变量,只要它是整数。记住,b
是对a
的引用。如果a
是一个列表,而f
的操作是像b.append(3)
那样,那么a
和b
在外部都是可用的,并且会被修改。
选择方法的不同是因为需求不同。
如果一个函数只是读取一个变量的值,那么这个变量就被认为是全局的。如果这个函数对变量进行了写入操作,那么这个变量就被认为是局部的。在你的第二个函数中,变量a被写入了,所以它被认为是局部变量。这样一来,上面那行读取变量a的代码就不合法了。
这里有一个关于Python常见问题的链接:http://docs.python.org/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python