【问题标题】:python noobie scoping questionpython noobie 范围问题
【发布时间】:2011-05-29 23:11:49
【问题描述】:

我写了这段代码:

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

【问题讨论】:

标签: python scoping


【解决方案1】:

来自the docs

Python 的一个特殊之处在于——如果 没有全局声明生效—— 名称的分配总是进入 最里面的范围。作业做 不复制数据——它们只是绑定名称 到对象。

所以,当 Python 解析时

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

它看到了x += 1 的赋值,因此决定x 必须在最内层范围内。当你稍后通过y() 调用temp(...) 时——(顺便说一句,self 应该从temp 的定义中省略,否则y() 应该提供一个参数)——Python 遇到print x 语句并发现 x 尚未在本地(最内层)范围内定义。因此出现错误,

UnboundLocalError: local variable 'x' referenced before assignment

如果你声明

 def temp(self):
     global x

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

 def temp(self):
     nonlocal x

【讨论】:

    【解决方案2】:

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

    (全局命名空间也很特别,因为它实际上既是模块全局命名空间,又是 builtin 命名空间,隐藏在 builtins__builtins__ 模块中。)

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

    如果您打算将所有这三个 x 引用都引用到全局变量,则需要在 countertemp 函数中都使用 global x。在 Python 3.x(但不是 2.x)中,有一个类似于 globalnonlocal 声明,您可以使用它使 temp 分配给 counter 中的变量,但保留全局 x一个人。

    【讨论】:

      【解决方案3】:

      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)
      

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

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

      【讨论】:

      • 无论 Python 人怎么称呼它,你忘记在 Python 函数中初始化的变量都具有动态范围。这是 Python 最明显的缺陷之一(不一定是动态范围,但它有时是动态范围)。
      • @ChrisPacejo:“动态范围”(相对于词法范围)在什么意义上?
      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        >>> 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-05-24
          • 2011-11-21
          • 1970-01-01
          • 1970-01-01
          • 2018-12-06
          • 2016-12-18
          • 2010-10-05
          相关资源
          最近更新 更多