当我尝试这段代码:
a, b, c = (1, 2, 3)
def test():
print(a)
print(b)
print(c)
c += 1
test()
我从打印(c)行得到一个错误,它说:
UnboundLocalError: local variable 'c' referenced before assignment
在Python的新版本中,或者
UnboundLocalError: 'c' not assigned
在一些老版本中。
如果注释掉c += 1,两次打印都成功。
我不明白:如果c不行,为什么打印a和b可以?c += 1是如何导致print(c)失败的,即使它出现在代码的后面?
赋值c += 1似乎创建了一个局部变量c,它优先于全局变量c。但是一个变量如何在它存在之前“窃取”作用域呢?为什么c是局部的?
请参见在函数中使用全局变量,了解如何从函数中重新分配全局变量的问题,以及是否可以在python中修改位于外部(封闭)但不是全局范围的变量?用于从封闭函数(闭包)重新赋值。
参见为什么不需要'global'关键字来访问全局变量?对于OP预期错误但没有得到错误的情况,从简单地访问一个没有global关键字的全局变量。
参见如何在Python中“解除绑定”名称?什么代码可以导致“UnboundLocalError”?对于OP期望变量是本地的,但在每种情况下都有阻止赋值的逻辑错误的情况。
看一看这个分解程序可能会清楚发生了什么:
>>> 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
As you can see, the bytecode for accessing a is LOAD_FAST, and for b, LOAD_GLOBAL. This is because the compiler has identified that a is assigned to within the function, and classified it as a local variable. The access mechanism for locals is fundamentally different for globals - they are statically assigned an offset in the frame's variables table, meaning lookup is a quick index, rather than the more expensive dict lookup as for globals. Because of this, Python is reading the print a line as "get the value of local variable 'a' held in slot 0, and print it", and when it detects that this variable is still uninitialised, raises an exception.
在下面n = num的情况下,n是一个局部变量,num是一个全局变量:
num = 10
def test():
# ↓ Local variable
n = num
# ↑ Global variable
print(n)
test()
因此,没有错误:
10
但是在下面的num = num的情况下,两边的num是局部变量,右边的num还没有定义:
num = 10
def test():
# ↓ Local variable
num = num
# ↑ Local variable not defined yet
print(num)
test()
所以,有下面的错误:
UnboundLocalError:赋值前引用的本地变量'num'
此外,即使删除num = 10,如下所示:
# num = 10 # Removed
def test():
# ↓ Local variable
num = num
# ↑ Local variable not defined yet
print(num)
test()
下面是同样的错误:
UnboundLocalError:赋值前引用的本地变量'num'
因此,为了解决上述错误,将global num放在num = num之前,如下所示:
num = 10
def test():
global num # Here
num = num
print(num)
test()
这样,上述误差就得到了解决,如下图所示:
10
或者,在num = num之前定义局部变量num = 5,如下所示:
num = 10
def test():
num = 5 # Here
num = num
print(num)
test()
这样,上述误差就得到了解决,如下图所示:
5
这不是对你的问题的直接回答,但它是密切相关的,因为这是由增广赋值和函数作用域之间的关系引起的另一个问题。
在大多数情况下,你倾向于认为增广赋值(a += b)与简单赋值(a = a + b)完全等价。不过,在一个角落的情况下,这可能会遇到一些麻烦。让我解释一下:
Python简单赋值的工作方式意味着,如果a被传递给一个函数(如func(a);注意Python总是引用传递),那么a = a + b将不会修改传入的a。相反,它只会修改指向a的局部指针。
但如果你使用a += b,那么它有时会实现为:
a = a + b
或者有时(如果方法存在)为:
a.__iadd__(b)
在第一种情况下(只要a没有声明为全局的),在局部作用域之外没有副作用,因为对a的赋值只是一个指针更新。
在第二种情况下,a实际上会修改自己,因此所有对a的引用都指向修改后的版本。下面的代码演示了这一点:
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
因此,诀窍是避免对函数参数进行扩展赋值(我尝试只对局部/循环变量使用它)。使用简单的赋值,就不会出现模棱两可的行为。