嵌套函数中的变量范围

时间:2013-05-09 01:33:26

标签: python scope nested-function

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

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 个答案:

答案 0 :(得分:4)

  

但是这个答案说问题在于分配给x。如果就是这样,   然后打印它应该工作得很好,不应该吗?

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

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

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

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

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

print语句看起来应该继续并在封闭范围内打印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标记为局部变量。

答案 1 :(得分:3)

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

在你的第二个例子而不是赋值中你只是改变了一个可变对象,所以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

在py3x中使用nonlocal

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

main()()  #prints 2

答案 2 :(得分:1)

问题是变量x是通过闭包来获取的。当您尝试分配从闭包中拾取的变量时,python会抱怨,除非您使用globalnonlocal 1 关键字。在使用list的情况下,您没有指定名称 - 您可以修改在闭包中拾取的对象,但不能分配给它。


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

通常,这会导致NameError,但是python尝试通过在xfunc_closure 2中查找func_globals来获得更多帮助。如果它在其中一个中找到x,则会引发UnboundLocalError而不是让您更好地了解正在发生的事情 - 您有一个无法找到的局部变量(不是“界”)。

1 仅限python3.x

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

答案 3 :(得分:0)

问题出在

x = x + 1

这是第一次在函数x中分配f(),告诉编译器x是本地名称。它与前一行print x冲突,后者无法找到本地x的任何先前分配。 这就是您的错误UnboundLocalError: local variable 'x' referenced before assignment来自的地方。

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

将其更改为

x[0] = x[0] + 1

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