Python 首次使用后重新分配局部变量时出现UnboundLocalError
以下代码在Python 2.5和3.0中都能正常工作:Python 首次使用后重新分配局部变量时出现UnboundLocalError,python,variables,scope,Python,Variables,Scope,以下代码在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)行时,我在第(A)行得到一个未绑定的本地错误:“c”未分配。a和b的值打印正确。这让我完全困惑,原因有两个: 为什么第(a)行出现运行时错误,因为第(B)行后面有一
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
但是,当我取消注释第(B)行时,我在第(A)行得到一个未绑定的本地错误:“c”未分配。a
和b
的值打印正确。这让我完全困惑,原因有两个:
为什么第(a)行出现运行时错误,因为第(B)行后面有一条语句
为什么变量a
和b
按预期打印,而c
会引发错误
我能给出的唯一解释是,局部变量c
是通过赋值c+=1
创建的,即使在创建局部变量之前,它也先于“全局”变量c
。当然,变量在存在之前“窃取”作用域是没有意义的
有人能解释一下这种行为吗?Python根据您是从函数内部还是外部给变量赋值,对函数中的变量进行不同的处理。如果在函数中指定了变量,则默认情况下将其视为局部变量。因此,当您取消注释该行时,您正试图在为其指定任何值之前引用局部变量c
如果希望变量c
引用函数前分配的全局c=3
,请将
global c
作为函数的第一行
至于Python3,现在有
nonlocal c
可用于引用最近的包含c
变量的封闭函数作用域。Python根据您是从函数内部还是外部为函数中的变量赋值而对其进行不同的处理。如果在函数中指定了变量,则默认情况下将其视为局部变量。因此,当您取消注释该行时,您正试图在为其指定任何值之前引用局部变量c
如果希望变量c
引用函数前分配的全局c=3
,请将
global c
作为函数的第一行
至于Python3,现在有
nonlocal c
可以用来引用最近的包含c
变量的封闭函数作用域。当您尝试传统的全局变量语义时,Python具有相当有趣的行为。我不记得细节了,但是您可以很好地读取在“global”范围中声明的变量的值,但是如果您想修改它,您必须使用global
关键字。尝试将test()
更改为:
def test():
global c
print(a)
print(b)
print(c) # (A)
c+=1 # (B)
此外,出现此错误的原因是,您还可以在该函数中声明一个与“全局”变量同名的新变量,并且它将是完全独立的。解释器认为您正在尝试在这个范围内创建一个名为c
的新变量,并在一个操作中对其进行修改,这在Python中是不允许的,因为这个新的c
没有初始化。当您尝试传统的全局变量语义时,Python具有相当有趣的行为。我不记得细节了,但是您可以很好地读取在“global”范围中声明的变量的值,但是如果您想修改它,您必须使用global
关键字。尝试将test()
更改为:
def test():
global c
print(a)
print(b)
print(c) # (A)
c+=1 # (B)
此外,出现此错误的原因是,您还可以在该函数中声明一个与“全局”变量同名的新变量,并且它将是完全独立的。解释器认为您试图在这个范围内创建一个名为c
的新变量,并在一个操作中对其进行修改,这在Python中是不允许的,因为这个新的c
没有初始化。Python有点奇怪,因为它将所有内容都保存在不同范围的字典中。原文a、b、c在最上面的范围内,因此在最上面的字典中。该函数有自己的字典。当到达print(a)
和print(b)
语句时,字典中没有该名称,因此Python会查找列表并在全局字典中找到它们
现在我们来看看c+=1
,当然,这相当于c=c+1
。当Python扫描该行时,它会说“啊哈,有一个名为c的变量,我将把它放在我的局部作用域字典中。”然后当它在赋值的右侧为c查找c的值时,它会找到名为c的局部变量,该变量还没有值,因此抛出错误
上面提到的语句global c
只是告诉解析器它使用来自全局范围的c
,因此不需要新的
它之所以说这行代码有问题,是因为它在尝试生成代码之前有效地查找了名称,所以从某种意义上说,它认为它还没有真正做到这一行。我认为这是一个可用性缺陷,但一般来说,学习不要把编译器的消息看得太重是一个很好的实践
如果有什么安慰的话,我可能花了一天的时间挖掘和试验同一个问题,然后才发现Guido写的关于解释一切的词典的东西
更新,见评论:
它不会扫描代码两次,但会分两个阶段扫描代码,词法分析和解析
考虑一下这行代码的解析是如何工作的。词法分析器读取源文本并将其分解为词素,即语法的“最小组成部分”。所以当它到达终点时
c+=1
它把它分解成这样的东西
SYMBOL(c) OPERATOR(+=) DIGIT(1)
解析器最终希望将其生成一个解析树并执行它,但由于它是一个赋值,因此在它之前,它会在本地字典中查找名称c,但没有看到它,并将其插入到
def copy_on_write(a):
a = a + a
def inplace_add(a):
a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1
>>> 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)
bar = 42
def foo():
print bar
if False:
bar = 0
class Employee:
counter=0
def __init__(self):
Employee.counter+=1
my_variables = { # a mutable object
'c': 3
}
def test():
my_variables['c'] +=1
test()