8

我写了这段代码:

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这样的词法范围语言肯定会起作用。

4

5 回答 5

10

文档

Python 的一个特殊之处是——如果没有全局语句生效——对名称的赋值总是进入最内部的范围。赋值不会复制数据——它们只是将名称绑定到对象。

所以,当 Python 解析

 def temp(self):
  print x
  x += 1

它看到分配x += 1,因此决定它x必须在最内部的范围内。当您稍后调用temp(...)via y()-- (顺便说一下,self应该从定义中省略temp或者y()应该提供一个参数)- Python 遇到该print x语句并发现x尚未在本地(最内层)范围中定义的语句. 因此错误,

UnboundLocalError: local variable 'x' referenced before assignment

如果你声明

 def temp(self):
     global x

然后 Python 将x在全局范围(where x=0)中查找。在 Python2 中,没有办法告诉 Pythonx在扩展范围(where x=1)中查找。但是在 Python3 中,可以通过声明来实现

 def temp(self):
     nonlocal x
于 2011-05-29T23:32:05.697 回答
6

Python 有两个作用域(实际上是三个)加上嵌套局部作用域的特殊规则。这两个作用域是global,用于模块级名称,而local,用于函数中的任何内容。您在函数中分配的任何内容都是自动本地的,除非您使用该函数中global的语句另外声明它。如果您使用的名称未分配给函数中的任何位置,则它不是本地名称;Python 将(在词法上)搜索嵌套函数以查看它们是否将该名称作为本地名称。如果没有嵌套函数或名称在其中任何一个中都不是本地的,则假定该名称是全局的。

(全局命名空间也很特殊,因为它实际上是模块全局命名空间和内置命名空间,它隐藏在builtinsor__builtins__模块中。)

在您的情况下,您有三个 x变量:一个在模块(全局)范围内,一个在counter函数中,一个在temp函数中 - 因为+=也是一个赋值语句。因为分配给名称的事实使它成为函数的本地变量,所以您的+=语句将尝试使用尚未分配给的局部变量,这将引发 UnboundLocalError。

如果您打算让所有这三个x引用都引用全局变量,则需要global x在 thecountertemp函数中都这样做。在 Python 3.x(但不是 2.x)中,有一个nonlocal声明非常类似于global您可以使用它temp来对变量 in 进行分配counter,但不理会全局x

于 2011-05-29T23:33:44.653 回答
2

Python 中的闭包是不可写的,所以你不能那样写代码。如果你只从函数内部的变量中读取,你应该很好。在 Python 3 中,您可以使用nonlocal关键字来获得您想要的行为。

如果您使用的是 Python 2.5 或更高版本,您还可以使用 yield 关键字将上述代码编写为生成器。

于 2011-05-29T23:30:33.997 回答
2

Python 文档详细回答了这个问题:http: //docs.python.org/reference/executionmodel.html#naming-and-binding

总之,Python 有静态作用域规则。如果函数 f 定义或删除了一个变量名,那么该变量名指的是函数 f 闭包中的一个变量。如果函数 f 只使用一个变量名(没有定义或删除),那么这个名字指的是这个名字在 f 的父作用域中的意思。继续上升到父范围,直到找到定义或到达全局范围。例如:

def f1(x):
  def g(y):
    z.foo = y # assigns global z
  g(1)

def f2(x):
  def g(y):
    x.foo = y # assigns f2's variable, because f2 defines x 
  g(1)

def f3(x):
  def g(y):
    x = C()
    x.foo = y # assigns g's variable, because g defines x
  g(1)

关键字和(在globalPython 3 中)nonlocal关键字会覆盖默认的范围规则。

虽然变量是静态解析的,但变量是动态解析的。变量的值来自访问该变量时对该变量的最新定义或删除。在变量所在的函数闭包中查找值。

于 2011-05-29T23:37:45.830 回答
1

感谢你的回复。以防万一有人关心,我想出了一个解决这个问题的方法。我通过创建一个“作用域”函数来建立一个计数器变量来做到这一点。

>>> def gc():
...  def scoper():
...   scoper.s = 0
...   def rtn():
...    scoper.s += 1
...    return scoper.s
...   return rtn
...  return scoper()

以上允许我这样做,它模仿了正确的闭包:

>>> a = gc()
>>> a()
1
>>> a()
2
>>> a()
3
>>> b = gc()
>>> b()
1
>>> a()
4
>>> b()
2
于 2011-05-30T08:04:08.977 回答