在嵌套函数变量范围(Variable scope in nested functions)

2019-09-02 08:06发布

有人能解释为什么下面的程序将失败:

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是一个数组?

谢谢。

Answer 1:

但这种回答说这个问题是与分配给X。 如果是这样的话,然后打印它应该工作得很好,应该不是吗?

你必须懂得在事情发生的顺序。 之前你的Python代码甚至是编译和执行,一些所谓的解析器通过Python代码读取和检查语法。 解析器做的另一件事是标志变量为本地。 当解析器看到在一个本地范围的代码的分配,在分配的左侧的变量被标记为本地。 在这一点上,没有什么更被编译,但 - 更不用说执行,因此没有分配发生; 可变仅仅标记为一个局部变量。

解析器完成后,该代码被编译并执行。 当执行到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语句看起来应该继续前进,在封闭范围(即“E”在LEGB查询过程)打印X。 然而,因为解析器先前标记x作为内部为f的本地变量(),蟒不进行过去本地范围(中LEGB查找过程“L”)查找X。 因为x尚未在“打印X”执行时间局部范围分配,蟒蛇吐出一个错误。

需要注意的是即使一个地方发生的分配将永远不会执行的代码,解析器还是标志着一个任务作为一个局部变量的左边的变量。 解析器没有的事情将如何执行的想法,所以一味的搜索语法错误和局部变量的整个文件 - 即使是在代码不能执行。 下面是一些例子:

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

当语句“打印X”执行,因为解析器标x作为一个局部变量x的查找停止在局部范围。

这“功能”是不是唯一的蟒蛇 - 它发生在其他语言中也。

至于数组例如,当你写:

x[0] = x[0] + 1

告诉蟒蛇去查找了一个数组命名为x和分配的东西给它的第一个元素。 因为没有分配给名为x在局部范围内任何东西,解析器并不标志着x作为一个局部变量。



Answer 2:

其原因是在第一示例中,您使用的分配操作, x = x + 1 ,所以函数被定义时蟒以为x是局部变量。 但是,当你真正调用的函数蟒蛇未能找到任何的价值x本地的RHS,所以提出了一个错误。

在你的第二个例子,而不是分配你只需改变一个可变的对象,所以Python将永远不会提出任何异议,并取x[0]从封闭范围内的值(实际上它首先查找它在封闭的范围,那么全球范围终于在建宏,但一旦停止,因为被发现)。

在Python 3倍,你可以使用外地关键字进行处理并在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


Answer 3:

问题是,变量x被关闭回升。 当你试图给正在从封闭拿起一个变量,蟒蛇会抱怨,除非你使用globalnonlocal 1个关键字。 在您使用的情况list ,你并没有使用到的名字-您可以修改它得到了在封闭拿起一个对象,但你不能分配给它。


基本上,发生错误的print x线,因为当蟒解析功能,它把该x到则认为被指定x必须是一个局部变量。 当你到行print x ,蟒蛇尝试查找本地x ,但它不存在。 这可以通过使用可以看出dis.dis检查字节码。 在这里,Python使用LOAD_FAST它用于本地变量,而不是指令LOAD_GLOBAL这是用于非局部变量的指令。

通常情况下,这会导致一个NameError ,但蟒蛇尝试多一点有益通过寻找xfunc_closurefunc_globals 2。 如果发现x在其中的一个,它会引发UnboundLocalError ,而不是给你什么正在发生一个更好的主意-你有可能无法找到一个局部变量(不是“绑定”)。

只有1个 python3.x

2 python2.x -在python3.x,这些属性已经改变到__closure____globals__分别



Answer 4:

问题是在该行

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



文章来源: Variable scope in nested functions