Python 新手作用域问题

8 投票
5 回答
3444 浏览
提问于 2025-04-16 18:35

我写了这段代码:

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 个回答

2

在Python中,闭包是不能被写入的,所以你不能按照那种方式来写代码。如果你只是想在函数内部读取变量,那就没问题。在Python 3中,你可以使用nonlocal这个关键词来实现你想要的效果。

如果你使用的是Python 2.5或更高版本,你也可以使用yield这个关键词,把上面的代码写成一个生成器。

6

在Python中,有两种作用域(其实是三种),还有一些特殊规则适用于嵌套的局部作用域。这两种作用域分别是全局作用域,指的是模块级别的名字,以及局部作用域,指的是函数内部的内容。在函数中你赋值的任何东西,默认都是局部的,除非你在那个函数里用global语句特别声明它是全局的。如果你在函数里使用一个没有被赋值的名字,它就不是局部名字;Python会去查找嵌套的函数,看看它们是否有这个名字作为局部名字。如果没有嵌套函数,或者在任何嵌套函数中这个名字也不是局部的,那么这个名字就被认为是全局的。

全局命名空间也很特别,因为它实际上既是模块的全局命名空间,也是内置命名空间,这个内置命名空间藏在builtins__builtins__模块里。

在你的例子中,有三个 x变量:一个在模块的全局作用域,一个在counter函数里,还有一个在temp函数里——因为+=也是一个赋值语句。由于赋值的行为让这个名字在函数里变成局部的,所以你的+=语句会试图使用一个还没有被赋值的局部变量,这样就会引发一个UnboundLocalError错误。

如果你希望这三个x引用都指向全局变量,你需要在countertemp函数里都写global x。在Python 3.x(而不是2.x)中,有一个nonlocal声明,跟global很像,你可以用它来让temp函数给counter里的变量赋值,但不影响全局的x

10

来自文档的内容:

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

撰写回答