7

有人可以解释为什么以下程序失败:

def g(f):
  for _ in range(10):
    f()

def main():
  x = 10
  def f():
    print x
    x = x + 1
  g(f)

if __name__ == '__main__':
  main()

留言:

Traceback (most recent call last):
  File "a.py", line 13, in <module>
    main()
  File "a.py", line 10, in main
    g(f)
  File "a.py", line 3, in g
    f()
  File "a.py", line 8, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

但是,如果我只是将变量更改x为数组,它就可以工作:

def g(f):
  for _ in range(10):
    f()

def main():
  x = [10]
  def f():
    print x[0]
    x[0] = x[0] + 1
  g(f)

if __name__ == '__main__':
  main()

与输出

10
11
12
13
14
15
16
17
18
19

我感到困惑的原因是,如果从中f()无法访问x,为什么如果x是数组则可以访问?

谢谢。

4

4 回答 4

5

但是这个答案说问题在于分配给x。如果是这样,那么打印它应该可以正常工作,不是吗?

你必须了解事情发生的顺序。在你的 python 代码甚至被编译和执行之前,一个叫做解析器的东西会读取 python 代码并检查语法。解析器所做的另一件事是将变量标记为本地变量。当解析器在本地范围内的代码中看到赋值时,赋值左侧的变量被标记为本地。那时,甚至还没有编译任何东西——更不用说执行了,因此没有发生赋值;该变量仅被标记为局部变量。

解析器完成后,代码被编译并执行。当执行到达打印语句时:

def main():
  x = 10     #<---x in enclosing scope

  def f():
    print x    #<-----

    x = x + 1  #<-- x marked as local variable inside the function f()

打印语句看起来应该继续并在封闭范围内打印 x(LEGB 查找过程中的“E”)。然而,因为解析器之前将 x 标记为 f() 中的局部变量,python 不会继续越过局部范围(LEGB 查找过程中的“L”)来查找 x。因为在执行 'print x' 时 x 尚未在本地范围内分配,所以 python 吐出一个错误。

请注意,即使发生赋值的代码永远不会执行,解析器仍然会将赋值左侧的变量标记为局部变量。解析器不知道事情将如何执行,因此它盲目地在整个文件中搜索语法错误和局部变量——即使是在永远无法执行的代码中。以下是一些例子:

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            a b c  #...yet the parser finds a syntax error here


    return f

f = dostuff()
f()



--output:--
File "1.py", line 8
     a b c
      ^
SyntaxError: invalid syntax

解析器在标记局部变量时做同样的事情:

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            x = 0  #..yet the parser marks x as a local variable

    return f

f = dostuff()
f()

现在看看当你执行最后一个程序时会发生什么:

Traceback (most recent call last):
  File "1.py", line 11, in <module>
    f()
  File "1.py", line 4, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

当语句 'print x' 执行时,因为解析器将 x 标记为局部变量,所以对 x 的查找会在局部范围内停止。

这个“特性”并不是python独有的——它也发生在其他语言中。

至于数组示例,当您编写时:

x[0] = x[0] + 1

这告诉 python 去查找一个名为 x 的数组并将某些东西分配给它的第一个元素。因为在局部范围内没有对任何名为 x 的东西进行赋值,所以解析器不会将 x 标记为局部变量。

于 2013-05-09T02:23:02.167 回答
4

原因是在第一个示例中您使用了赋值操作,x = x + 1所以当定义函数时,python 认为这x是局部变量。但是当你实际调用函数时,python 未能在x本地找到 RHS 上的任何值,所以引发了一个错误。

在您的第二个示例中,您只是更改了一个可变对象而不是赋值,因此 python 永远不会提出任何异议,并且会x[0]从封闭范围中获取 的值(实际上它首先在封闭范围中查找它,然后是全局范围,最后在内置,但一被发现就停止)。

在 python 3x 中,您可以使用 nonlocal 关键字来处理这个问题,而在 py2x 中,您可以将值传递给内部函数或使用函数属性。

使用函数属性:

def main():
  main.x = 1
  def f():
      main.x = main.x + 1
      print main.x
  return f

main()()   #prints 2

显式传递值:

def main():
  x = 1
  def f(x):
      x = x + 1
      print x
      return x
  x = f(x)     #pass x and store the returned value back to x

main()   #prints 2

nonlocal在 py3x 中使用:

def main():
  x = 1
  def f():
      nonlocal x
      x = x + 1
      print (x)
  return f

main()()  #prints 2
于 2013-05-09T01:41:43.733 回答
1

问题是变量x被闭包拾取。当您尝试分配从闭包中提取的变量时,python 会抱怨,除非您使用globalor nonlocal1关键字。在您使用 a 的情况下list,您没有分配名称——您可以修改在闭包中拾取的对象,但不能分配给它。


基本上,错误发生在该print x行,因为当 python 解析函数时,它看到它x被分配给所以它假设x必须是一个局部变量。当你到达 lineprint x时,python 会尝试查找本地x但它不存在。这可以通过使用dis.dis检查字节码来看到。这里,python 使用LOAD_FAST用于局部变量的指令,而不是LOAD_GLOBAL用于非局部变量的指令。

通常,这会导致 a ,但 python 会尝试通过查找in或2NameError来提供更多帮助。如果它在其中一个中找到,它会引发 an来让您更好地了解正在发生的事情——您有一个找不到的局部变量(不是“绑定”)。xfunc_closurefunc_globals xUnboundLocalError

1仅限 python3.x

2 python2.x -- 在 python3.x 上,这些属性已分别更改为__closure____globals__

于 2013-05-09T01:35:12.660 回答
0

问题出在一线

x = x + 1

这是第一次x在 function 中赋值f(),告诉编译器这x是一个本地名称。它与上一行冲突,上一行print x找不到本地的任何先前分配x。这就是你的错误的UnboundLocalError: local variable 'x' referenced before assignment来源。

请注意,当编译器试图找出xinprint x所指的对象时,就会发生错误。所以print x不会执行。

将其更改为

x[0] = x[0] + 1

没有添加新名称。所以编译器知道你指的是外面的数组f()

于 2013-05-09T01:50:39.397 回答