Python变量范围错误
以下代码在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)处UnboundLocalError: 'c' not assigned
。 a
和b
的值被正确打印。 这让我完全困惑,原因有两个:
为什么由于线(B)后面的语句会在行(A)处引发运行时错误?
为什么变量a
和b
按预期打印,而c
会产生错误?
我可以想到的唯一解释是局部变量c
是由赋值c+=1
创建的,甚至在创建局部变量之前,该变量先于“全局”变量c
创建。 当然,变量在存在之前“偷窃”范围是没有意义的。
有人可以解释这种行为吗?
Python根据是否在函数中为它们分配值来区别对待函数中的变量。 如果函数包含对变量的任何赋值,则默认将其视为局部变量。 因此,当您取消注释该行时,您正试图在为其分配任何值之前引用局部变量。
如果你想让变量c
引用全局变量c
put
global c
作为函数的第一行。
至于python 3,现在有了
nonlocal c
您可以使用它来引用具有c
变量的最近的封闭函数作用域。
Python有点奇怪,因为它将所有内容都保存在各种范围的字典中。 原始的a,b,c在最上面的范围内,所以在最上面的字典中。 该函数有它自己的字典。 当你到达print(a)
和print(b)
语句时,字典中没有这个名字,所以Python查找列表并在全局字典中找到它们。
现在我们得到c+=1
,当然这相当于c=c+1
。 当Python扫描该行时,它说“啊哈,有一个名为c的变量,我将它放到我的本地范围字典中。” 然后,当它为赋值右边的c查找c的值时,它会找到名为c的局部变量,该变量尚无值,因此会引发错误。
上面提到的语句global c
只是告诉解析器它使用全局范围中的c
,因此不需要新的c
。
它说它存在一个问题是因为它在尝试生成代码之前正在有效地查找这些名称,所以从某种意义上来说,它并不认为它真的在这样做。 我认为这是一个可用性错误,但通常学习不要太认真对待编译器的消息是一个好习惯。
如果它有任何的安慰,我可能花了一天的时间来挖掘和试验这个同样的问题,然后我发现Guido写了关于解释一切的字典的东西。
更新,请参阅评论:
它不会扫描代码两次,但它确实在两个阶段扫描代码,lexing和解析。
考虑这段代码的解析是如何工作的。 词法分析器读取源文本并将其分解为词法,这是语法的“最小组件”。 所以当它击中线
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被分配给该函数,并将其分类为局部变量。 对于全局变量来说,局部变量的访问机制是根本不同的 - 它们在变量表中被静态地分配一个偏移量,这意味着查找是一个快速索引,而不是像全局变量那样的更昂贵的字典查找。 正因为如此,Python正在读取print a
行,如“获取槽0中保存的局部变量'a'的值并打印出来”,并且当它检测到此变量仍未初始化时,会引发异常。