有人可以解释为什么以下程序失败:
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
是一个数组就可以访问它?
感谢。
答案 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会抱怨,除非您使用global
或nonlocal
1 关键字。在使用list
的情况下,您没有指定名称 - 您可以修改在闭包中拾取的对象,但不能分配给它。
基本上,错误发生在print x
行,因为当python解析函数时,它会看到x
已分配给它,因此它假定x
必须是局部变量。当你到达print x
行时,python会尝试查找本地x
,但它不在那里。通过使用dis.dis
检查字节码可以看出这一点。这里,python使用用于局部变量的LOAD_FAST
指令而不是用于非局部变量的LOAD_GLOBAL
指令。
通常,这会导致NameError
,但是python尝试通过在x
或func_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
来自的地方。
请注意,当编译器试图找出x
中print x
引用的对象时,会发生错误。所以print x
不会执行。
将其更改为
x[0] = x[0] + 1
未添加新名称。所以编译器知道你指的是f()
之外的数组。