Python自由变量。这为什么会失败?

7 投票
4 回答
1992 浏览
提问于 2025-04-17 09:15

下面的代码会输出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 个回答

5

你所使用的东西叫做闭包:就是从外部范围拿一个变量,然后把它放在一个函数块里。

你的代码是完全没问题的,在JavaScript中可以正常工作。

可惜的是,在Python中,闭包是只读的。

而且你会遇到的错误信息总是UnboundLocalError: local variable 'var_name' referenced before assignment,这个错误信息其实很误导。

简单来说,这不是你的问题,而是语言的限制加上一个糟糕的错误提示

编辑:

我看到这里有好几个人建议使用global但这会有危险的副作用:你可能会访问到在当前作用域上方的多个作用域中同名的变量,这可不是你想要的闭包效果。

在Python 3中,解决方案是引入了nonlocal关键字,它正是用来在内部作用域中重新绑定外部作用域的变量。

对于Python 2.x,有一种方法可以模拟nonlocal,但其实你最好不要给你的变量赋值:只需复制值、返回值,或者只修改可变类型的值,这样就没问题了。

5

这是因为在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)那样,那么ab在外部都是可用的,并且会被修改。

选择方法的不同是因为需求不同。

8

如果一个函数只是读取一个变量的值,那么这个变量就被认为是全局的。如果这个函数对变量进行了写入操作,那么这个变量就被认为是局部的。在你的第二个函数中,变量a被写入了,所以它被认为是局部变量。这样一来,上面那行读取变量a的代码就不合法了。

这里有一个关于Python常见问题的链接:http://docs.python.org/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

撰写回答