Python中的UnboundLocalError是什么错误?如何解决?

如果代码报错UnboundLocalError, 大概率犯了以下错误:

money = 10000  # 当前存款


def add_money(value):
    money += value


if __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

分别定义了变量namesmoney.

在函数体内定义的变量是局部变量,反之,在函数体外定义的变量是全局变量。例如:

book_prices = {}


def get_book_price(book_name):
    value = book_prices.get(book_name, 0)
    return value

以上代码中,变量book_prices在函数体外定义,它是个全局变量;在函数get_book_price内,定义了变量value, 因此它是个局部变量。

但是,如果全局变量和局部变量同名,它们还是同一个变量吗?例如:

weight = 120


def print_size(n):
    weight = 0.2
    print(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 money
    money += value

修改后,代码输出我们期望的结果。

等等,如果止步于此,可能会形成这样一种错误的认识:只要在函数内部使用全局变量,就要使用global. 然而,在函数体内使用全局变量的需求很常见,但global并不常用。上面这句话错在哪儿?请接着往下看。

3. global并不常用

在函数体内,只有当全局变量出现在赋值运算的左侧时,才需要使用global. 例如在一开始的例子中,我们的目的是在函数add_money内修改全局变量money, money出现在赋值运算(+=)的左侧, 此时才需要使用global.

请看这个例子:

money = 10000


def 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信免费获取和问题答疑哦~
在这里插入图片描述

如有侵权,请联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值