如果代码报错UnboundLocalError
, 大概率犯了以下错误:
money = 10000 # 当前存款def add_money(value):money += valueif __name__ == '__main__':print('当前存款:', money)add_money(1000)print('当前存款:', money)
其中,变量money
表示当前存款;函数add_money
将变量money
加上指定的值value
. 对于新手,或许认为两条print
语句中的money
分别为10000和11000. 但很遗憾,这段代码运行后会报错:
当前存款: 10000
Traceback (most recent call last):
File “XXX”, line 10, in
add_money(1000)
File “XXX”, line 5, in add_money
money += value
UnboundLocalError: local variable ‘money’ referenced before assignment
可以看到,调用函数add_money
后抛出错误UnboundLocalError.
根据报错信息,局部变量(local variable)money
在赋值前被引用。那么,什么是局部变量?这个错误是如何产生的?
1. 局部变量与全局变量
在Python中,如果变量名出现在赋值运算**(=
)**的左侧,那么就定义了一个变量。例如:
names = ['你好', 1234]money = 11000
分别定义了变量names
和money
.
在函数体内定义的变量是局部变量,反之,在函数体外定义的变量是全局变量。例如:
book_prices = {}def get_book_price(book_name):value = book_prices.get(book_name, 0)return value
以上代码中,变量book_prices
在函数体外定义,它是个全局变量;在函数get_book_price
内,定义了变量value
, 因此它是个局部变量。
但是,如果全局变量和局部变量同名,它们还是同一个变量吗?例如:
weight = 120def print_size(n):weight = 0.2print(weight * n)
可以看到,以上代码分别定义了全局变量weight
, 在函数print_size
内也定义了同名的局部变量weight
. 稍加实验可以发现,这两个变量并不是同一个变量,也就是说,当调用函数print_size
后,全局变量weight
的值依然是120, 而不是0.2.
Python为何如此设计呢?想一想,如果名字相同的都是同一个变量,当使用别人的代码库时,就要保证自己使用的变量名不能与别人用过的变量名重复,以避免在代码中修改这些变量。不难想象,这样的编程语言并不好用。
2. 问题解答
有了上述准备,现在可以回答一开始那段代码报错的原因了:
money = 10000 # 当前存款def add_money(value):money += value
首先,这段代码定义了全局变量money
. 另外,在函数add_money
中,语句money += value
等价于money = money + value
,变量money
出现在赋值运算的左侧,因此在函数体内也定义了局部变量money
.
小贴士
在Python的函数中,所有出现在赋值运算(=)左侧的变量都被Python视为局部变量,仅在函数内部可用。
现在,在函数add_money
中,变量money
被识别为局部变量,然而,对于语句money = money + value
, 这条语句运行完毕后才会定义变量money
, 然而,变量money
的创建需要用到money
自身,这让我们联想到“祖父悖论”。对于这类错误,Python就会抛出UnboundLocalError.
但是,我们的本意是在函数add_money
内修改全局变量money
,有没有办法做到这一点?答案是使用关键字global
. 通过在函数体内使用global
,告诉Python, 此处修改的是全局变量. 上述代码修改如下:
money = 10000 # 当前存款def add_money(value):global moneymoney += value
修改后,代码输出我们期望的结果。
等等,如果止步于此,可能会形成这样一种错误的认识:只要在函数内部使用全局变量,就要使用global. 然而,在函数体内使用全局变量的需求很常见,但global
并不常用。上面这句话错在哪儿?请接着往下看。
3. global并不常用
在函数体内,只有当全局变量出现在赋值运算的左侧时,才需要使用global
. 例如在一开始的例子中,我们的目的是在函数add_money
内修改全局变量money
, money
出现在赋值运算(+=
)的左侧, 此时才需要使用global
.
请看这个例子:
money = 10000def print_money():print('当前余额:', money)
以上代码中,调用函数print_money
后,会输出10000. 也就是说,函数print_money
内使用的是全局变量money
. 因为在函数体内,变量money
并没有出现在赋值运算的左侧,也就是说,没有在函数体内定义局部变量。
闭包
像Python, JavaScript这样的动态语言(它们允许在函数体内定义函数)都有闭包特性,当函数需要用到一个变量时,会从内而外一层层地查找变量,直到处于最外层的全局变量。
再来看一个例子:
books = ["If give me 3 days' light", "Fluent Python"]def modify_first_book():books[0] = "Keep alive"if __name__ == '__main__':print("修改前:", books)modify_first_book()print("修改后:", books)
调用函数modify_first_book
后,可以看到全局变量books
的第0项被修改。注意,在这里的函数modify_first_book
中,出现在赋值运算左侧的是books[0]
, 这不是一个变量名,而是对列表books
第0项的引用。
为了对比,稍微修改函数modify_first_book
:
books = ["If give me 3 days' light", "Fluent Python"]def modify_first_book():books = []if __name__ == '__main__':print("修改前:", books)modify_first_book()print("修改后:", books)
运行以上代码,可以发现全局变量books
并未被修改,因为在函数modify_first_book
中,出现在赋值运算左侧的是变量名books
,因此会创建一个局部变量books
.
现在可以回答为什么global
并不常用:我们很少在函数体内直接通过赋值运算符修改全局变量,这种情况大多发生于一些基本的类型,例如int, float, str
等等。对于list, dict
或是用户自定义的类,通常会调用它们的方法,例如list.append
, dict.get
, 很少通过赋值运算直接修改它们。当然,如果你这么写过,一定遇到过一些“谜之错误”:代码可以运行,但运行结果总是不符合预期。
Python中还有个关键字nonlocal
, 用起来和global
差不多,在此不再赘述。
需要Python学习资料和python接单技巧的可以添加小助手w信免费获取和问题答疑哦~
如有侵权,请联系删除。