Python 新手作用域问题
我写了这段代码:
x = 0
def counter():
x = 1
def temp(self):
print x
x += 1
return temp
我想测试一下Python是使用词法作用域还是动态作用域。我想的方式是:
y = counter()
y()
应该打印出0或1,这样就能告诉我Python的作用域是怎样的。然而,调用y的时候却抛出了一个异常,提示x未定义。看来我对Python的理解有些根本性的错误。
有人能解释一下这是怎么回事吗?是的,我知道这可以很容易地通过对象来实现。我想探索一下在不使用对象的情况下,给函数赋予状态的想法。我写这段代码的方式是因为如果用像Scheme这样的词法作用域语言来写,肯定是可以正常工作的。
5 个回答
在Python中,闭包是不能被写入的,所以你不能按照那种方式来写代码。如果你只是想在函数内部读取变量,那就没问题。在Python 3中,你可以使用nonlocal
这个关键词来实现你想要的效果。
如果你使用的是Python 2.5或更高版本,你也可以使用yield这个关键词,把上面的代码写成一个生成器。
在Python中,有两种作用域(其实是三种),还有一些特殊规则适用于嵌套的局部作用域。这两种作用域分别是全局作用域,指的是模块级别的名字,以及局部作用域,指的是函数内部的内容。在函数中你赋值的任何东西,默认都是局部的,除非你在那个函数里用global
语句特别声明它是全局的。如果你在函数里使用一个没有被赋值的名字,它就不是局部名字;Python会去查找嵌套的函数,看看它们是否有这个名字作为局部名字。如果没有嵌套函数,或者在任何嵌套函数中这个名字也不是局部的,那么这个名字就被认为是全局的。
全局命名空间也很特别,因为它实际上既是模块的全局命名空间,也是内置命名空间,这个内置命名空间藏在builtins
或__builtins__
模块里。
在你的例子中,有三个 x
变量:一个在模块的全局作用域,一个在counter
函数里,还有一个在temp
函数里——因为+=
也是一个赋值语句。由于赋值的行为让这个名字在函数里变成局部的,所以你的+=
语句会试图使用一个还没有被赋值的局部变量,这样就会引发一个UnboundLocalError错误。
如果你希望这三个x
引用都指向全局变量,你需要在counter
和temp
函数里都写global x
。在Python 3.x(而不是2.x)中,有一个nonlocal
声明,跟global
很像,你可以用它来让temp
函数给counter
里的变量赋值,但不影响全局的x
。
来自文档的内容:
Python有一个特别的特点,就是如果没有使用全局声明,那么对变量的赋值总是发生在最内层的作用域里。赋值并不是复制数据,而是将变量名和对象绑定在一起。
所以,当Python解析
def temp(self):
print x
x += 1
时,它看到赋值语句x += 1
,因此判断x
一定是在最内层的作用域中。当你之后通过y()
调用temp(...)
时——顺便说一下,要么在temp
的定义中省略self
,要么y()
需要提供一个参数——Python遇到print x
这条语句时,发现x
在局部(最内层)作用域中没有定义。因此就报错了,
UnboundLocalError: local variable 'x' referenced before assignment
如果你声明了
def temp(self):
global x
那么Python会在全局作用域中查找x
(也就是x=0
的地方)。在Python2中,没有办法让Python去查找扩展作用域中的x
(也就是x=1
的地方)。但在Python3中,可以通过声明来实现这一点
def temp(self):
nonlocal x