以下代码在Python 2.5和3.0中按预期工作:
a, b, c = (1, 2, 3) print(a, b, c) def test(): print(a) print(b) print(c) # (A) #c+=1 # (B) test()
但是,当我取消注释行(B)时,我得到了UnboundLocalError: 'c' not assigned
一行(A).的值a
和b
被正确地打印.这让我感到困惑,原因有两个:
为什么在行(A)处抛出运行时错误,因为后面的行(B)语句?
为什么变量a
和b
打印符合预期,同时c
引发错误?
我能想到的唯一解释是,赋值创建了一个局部变量,即使在创建局部变量之前,它也优先于"全局"变量.当然,变量在存在之前"窃取"范围是没有意义的.c
c+=1
c
有人可以解释一下这种行为吗?
Python会根据您是否从函数中为它们赋值来不同地处理函数中的变量.如果函数包含对变量的任何赋值,则默认情况下将其视为局部变量.因此,当您取消注释该行时,您将尝试在为其分配任何值之前引用局部变量.
如果您希望变量c
引用全局c
put
global c
作为功能的第一行.
至于python 3,现在有
nonlocal c
您可以用来引用具有c = 3
变量的最近的封闭函数范围.
Python有点奇怪,它将所有内容保存在各种范围的字典中.原始的a,b,c位于最上面的范围内,因此位于最上面的字典中.该函数有自己的字典.当你到达print(a)
和print(b)
语句时,字典中没有任何名称,所以Python查找列表并在全局字典中找到它们.
现在我们到达c+=1
,当然,相当于c=c+1
.当Python扫描该行时,它会显示"aha,有一个名为c的变量,我会将它放入我的本地范围字典中." 然后,当它为赋值右侧的c寻找c的值时,它会找到名为c的局部变量,该变量尚无值,因此抛出错误.
global c
上面提到的语句只是告诉解析器它使用c
了全局范围,因此不需要新的范围.
它之所以说它出现问题的原因是因为它在尝试生成代码之前有效地寻找名称,因此在某种意义上它并不认为它确实在做那条线.我认为这是一个可用性错误,但通常一个好习惯就是学会不要太认真地对待编译器的消息.
如果有任何安慰,我可能花了一天时间挖掘并尝试同样的问题,然后才发现Guido写的关于解释一切的词典.
它不会扫描代码两次,但它会分两个阶段扫描代码,lexing和parsing.
考虑一下这行代码的解析是如何工作的.词法分析器读取源文本并将其分解为词汇,即语法的"最小组件".所以,当它击中线
c+=1
它把它分解成类似的东西
SYMBOL(c) OPERATOR(+=) DIGIT(1)
解析器最终想要将它变成一个解析树并执行它,但由于它是一个赋值,在它之前,它会在本地字典中查找名称c,看不到它,并将其插入字典中,标记它没有初始化.在完全编译的语言中,它只会进入符号表并等待解析,但由于它不会有第二次传递的奢侈,因此词法分析器会做一些额外的工作以使以后的生活更轻松.只有,然后它才会看到操作者,看到规则说"如果你有一个操作员+ =左手边必须已经初始化"并说"哎呀!"
这里的要点是它还没有真正开始解析该行.这一切都发生在实际解析的准备上,所以行计数器还没有前进到下一行.因此,当它发出错误信号时,它仍然认为它在前一行.
正如我所说,你可以说这是一个可用性错误,但它实际上是一个相当普遍的事情.有些编译器对此更加诚实,并说"XXX线上或附近有错误",但这个没有.
看一下反汇编可能会澄清发生了什么:
>>> def f(): ... print a ... print b ... a = 1 >>> import dis >>> dis.dis(f) 2 0 LOAD_FAST 0 (a) 3 PRINT_ITEM 4 PRINT_NEWLINE 3 5 LOAD_GLOBAL 0 (b) 8 PRINT_ITEM 9 PRINT_NEWLINE 4 10 LOAD_CONST 1 (1) 13 STORE_FAST 0 (a) 16 LOAD_CONST 0 (None) 19 RETURN_VALUE
如您所见,用于访问a的字节码是LOAD_FAST
和b LOAD_GLOBAL
.这是因为编译器已经识别出在函数内分配了a,并将其归类为局部变量.本地化的访问机制对于全局变量是根本不同的 - 它们在帧的变量表中静态分配了一个偏移量,这意味着查找是一个快速索引,而不是像全局变量那样更昂贵的dict查找.正因为如此,Python正在读取该print a
行"获取局部变量的值'a'保存在插槽0中并打印它",并且当它检测到该变量仍然未初始化时,引发异常.
当您尝试传统的全局变量语义时,Python具有相当有趣的行为.我不记得细节,但是你可以很好地读取在'global'范围内声明的变量的值,但如果你想修改它,你必须使用global
关键字.尝试更改test()
为:
def test(): global c print(a) print(b) print(c) # (A) c+=1 # (B)
此外,您收到此错误的原因是因为您还可以在该函数内声明一个与"全局"同名的新变量,它将完全独立.解释器认为您正在尝试在此作用域中创建一个新变量,c
并在一个操作中对其进行全部修改,这在Python中是不允许的,因为这个新的c
未初始化.
明确的最好例子是:
bar = 42 def foo(): print bar if False: bar = 0
在调用时foo()
,这也会引发 UnboundLocalError
虽然我们永远不会到达行bar=0
,所以永远不应该创建逻辑局部变量.
神秘之处在于" Python是一种解释语言 ",函数的声明foo
被解释为单个语句(即复合语句),它只是笨拙地解释它并创建局部和全局范围.因此bar
在执行前在本地范围内被识别.
有关此类更多示例,请阅读以下文章:http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
这篇文章提供了Python变量的范围的完整描述和分析:
这里有两个可能有用的链接
1:docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2:docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
链接一描述错误UnboundLocalError.链接二可以帮助重写您的测试功能.根据链接二,原始问题可以改写为:
>>> a, b, c = (1, 2, 3) >>> print (a, b, c) (1, 2, 3) >>> def test (a, b, c): ... print (a) ... print (b) ... print (c) ... c += 1 ... return a, b, c ... >>> a, b, c = test (a, b, c) 1 2 3 >>> print (a, b ,c) (1, 2, 4)