在一个月黑风高的夜晚,我们满心欢喜地写出以下代码:

money = 10000  # 当前的财产,单位为元

def add_money(value):
money += value print('当前财产: ', money)
add_money(10)
print('当前财产: ', money)

  以上代码中,定义了函数add_money, 将money增加value. 我们期待着运行代码后,程序能够依次输出10000和10010. 可惜事与愿违,运行后程序会报以下错误:

  money += value
     ^^^^^

UnboundLocalError: cannot access local variable 'money' where it is not associated with a value

  根据报错信息,money += value这行代码出现了错误,报错为UnboundLocalError.:无法访问局部变量money. 什么是局部变量(local variable)?为了搞清楚这个问题,我们首先需要了解Python中变量的作用域。

1. 变量的作用域

  所谓的作用域,指的是可以访问到变量的范围。请看下面这个例子:

def add(a, b):
c = a + b
return c print(c)

  这段代码运行时会报错,编译器会提示未定义变量c:因为变量c定义在函数add内部,函数外是访问不到变量c的。因此,可以说变量c的作用域在函数add内部。

  再来看下面这段代码:

name = '高尔夫的基'

def print_name():
print(name) print_name()

  这段代码可以正常运行。变量name定义在函数print_name外部,但是函数能访问到这个变量。定义在模块顶层的变量,类似于这里的name,我们称之为全局变量(global variable).

如果你不太明白什么叫定义在模块顶层的变量,可以理解为在函数外定义的变量。

2. 局部变量

  现在,来看下面这段代码:

name = '高尔夫的基'

def print_name():
name = '赵奔三'
print(name) print_name()
print(name)

  上述代码运行后,输出如下:

赵奔三
高尔夫的基

  在函数print_name内,将变量name赋值为字符串 '赵奔三' ,因此调用print_name()后,会输出字符串'赵奔三', 这并不奇怪。“奇怪”的是,代码运行到print(name)后,会输出字符串'高尔夫的基', 看起来全局变量name没有受到函数print_name的影响,这是为什么呢?

  在Python中,当变量名出现在赋值运算符(也就是=,当然+=, -=这种也算)的左边时,会创建一个新的变量,这样的变量称为局部变量。例如,在函数print_name中,对于name = '赵奔三'这行代码,在=的左边出现了变量名name,Python就会创建一个局部变量name供函数print_name内部使用。

  为什么要这么设计呢?大家可以想一想,如果一个变量可以在代码的任意位置被随意修改,随着我们的代码越写越长,如果哪天不小心定义了一个同名的变量,那么这个变量到底代表什么呢?从而,我们的代码会更容易出错。

  说到这里,我必须强调一点:在函数体内部,当变量名出现在赋值运算符的左边时,才会创建局部变量。请看下面这个例子:

lst = [123, 'abc', 3.45]

def set_lst0(value):
lst[0] = value print(lst)
set_lst0('针不戳')
print(lst)

  以上代码运行后的输出为:

[123, 'abc', 3.45]
['针不戳', 'abc', 3.45]

  这里,函数set_lst0将列表lst(它是个全局变量)的第0项修改为传入的参数value. 可以看到,调用函数set_lst0后,列表lst的第0项成功被修改。究其原因,在函数set_lst中,对于语句lst[0] = value,=的左边lst[0]并不是变量名,因此谈不上创建局部变量,函数内使用的lst也就是全局变量lst.

3. 解决方法

  现在再来看看一开始的那行代码money += value,这行代码等价于money = money + value.

  这行代码位于函数add_money内,而且变量money出现在赋值运算符=的左边,因此Python认为money是一个局部变量,从而此函数内的变量money与函数外定义的全局变量money没有半毛钱关系。

  然而,在这行代码的右边也出现了变量money, 这会导致局部变量money还没有被创建呢,程序就要读取它的值,这不是耍流氓吗?所以会报错,这种错误就称为UnboundLocalError.

  但如果我们非要在函数内修改全局变量money呢?很简单,在函数内告诉Python, 我们这里使用的是全局变量money. 怎么告诉Python呢?通过global关键词告诉Python:

money = 10000  # 当前的财产,单位为元

def add_money(value):
global money
money += value print('当前财产: ', money)
add_money(10)
print('当前财产: ', money)

  现在运行以上程序,会输出预期的结果。

4. 为什么之前没遇到这种问题

  很多人困惑的是:自己以前好像从来没有遇到这种情况,怎么学到global这里突然就出现了这种问题?

  事实上,我们很少写出本文开始时的那种代码。例如,我们定义了一个全局变量a,它是一个空列表:

a = []

  

  如果需要在函数内部修改这个列表,多数情况下我们会使用a.append或者a.remove这些方法修改列表,例如:

def do_sth_a():
a.append(XXX)
...
a.remove(XXX)

  而不会写成:

def do_sth_a():
a = []

  或者:

def do_sth_a():
a = 123

  也就是说,你不大可能直接通过=修改列表a. 当然,如果你这么写过,那么你一定遇到过一些“谜之错误”,代码或许可以运行,但运行结果和你的预期总是不一样。

5. 小结

  编程专家告诉我们,在代码中慎用全局变量,因此关键字global并不受待见。如果你已经学到了类,那么用到global的概率会进一步降低。只有在极其特殊的几种情况下,global会很有用,不过大多数人在以后的编程生涯中都不会再用到它。我在学习global这个关键字后,也只在教别人的情况下用到过。

  Python中还有个关键字nonlocal, 用起来和global差不多,只是适用的条件不同:它用于函数内定义的嵌套函数,这里就不再细说了。

  如有错误之处,还请多多指正,Thanks!

Python中的UnboundLocalError是什么错误?如何解决?的更多相关文章

  1. Python编程的10个经典错误及解决办法

    接触了很多Python爱好者,有初学者,亦有转行人.不论大家学习Python的目的是什么,总之,学习Python前期写出来的代码不报错就是极好的.下面,严小样儿为大家罗列出Python3十大经典错误及 ...

  2. 在Linux下安装PHP过程中,编译时出现错误的解决办法

    在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...

  3. Mysql安装过程中出现apply security settings错误的解决方法

    在学习Mysql的过程中,首先要安装Mysql.然而在第一遍安装过程中难免会出现安装错误的时候,当卸载后第二次安装(或者第三次甚至更多次)的时候,往往在安装最后一步会出现apply security ...

  4. python中GraphViz's executables not found的解决方法以及决策树可视化

    出现GraphViz's executables not found报错很有可能是环境变量没添加上或添加错地方. 安装pydotplus.graphviz库后,开始用pydotplus.graph_f ...

  5. python中import cv2遇到的错误及安装方法

    参考链接:https://blog.csdn.net/yuanlulu/article/details/79017116 从x86_64 + ubuntu18.04 + python3.5中impor ...

  6. python中sys模块之输入输出错误流

    import sys sys.stdout.write("msg")   # 控制台白色字体打印 普通输出流 sys.stderr.write("msg") # ...

  7. 原创:解决 python中moviepy调用ffmpeg的错误:subprocess, PermissionError: [WinError 5] 拒绝访问

    近期运行一个python程序用到了moviepy.editor.VideoFileClip() moviepy基于ffmpeg,但是并不是pip安装的ffmepg, 执行 import imageio ...

  8. python中一个经典的参数错误

    直接上代码 class Company: def __init__(self, name, staffs=[]): self.name = name self.staffs = staffs def ...

  9. python文件读取:遇见的错误及解决办法

    问题一: TypeError: 'str' object is not callable 产生原因: 该错误TypeError: 'str' object is not callable字面上意思:就 ...

  10. VS2005 “无法在证书存储区中找到清单签名证书”错误的解决方法

    方法一:在VS2005中出现该错误时,用记事本打开项目的.csproj文件,删除以下内容即可:    <ManifestCertificateThumbprint>B531F2CF2227 ...

随机推荐

  1. 五分钟,手撸一个简单的Spring容器

    工厂和Spring容器Spring是一个成熟的框架,为了满足扩展性.实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现 ...

  2. 《深入理解计算机系统》(CSAPP)读书笔记 —— 第七章 链接

    链接( Clinking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行.链接可以执行于编译时( compile time),也就是在源代码被翻译成机器代 ...

  3. 终于!我们把 CEO 炒了,让 ChatGPT 出任 CEO

    ️ FBI Warning:本文纯属作者自娱自乐,数字人的观点不代表 CEO 本人的观点,请大家不要上当受骗!! 哪个公司的 CEO 不想拥有一个自己的数字克隆? 想象一下,如果 CEO 数字克隆上线 ...

  4. P1725-DP【绿】

    这道题最开始我用记搜写的,然后WA了一些点,后来看了半天才发现是数组开小了,原来他给了两个数据范围,一个是60%数据的数据范围,另一个是100%数据的数据范围.我没仔细看,没看见后面那行,把60%数据 ...

  5. Feign源码解析7:nacos loadbalancer不支持静态ip的负载均衡

    背景 在feign中,一般是通过eureka.nacos等获取服务实例,但有时候调用一些服务时,人家给的是ip或域名,我们这时候还能用Feign这一套吗? 可以的. 有两种方式,一种是直接指定url: ...

  6. 基于AHB_BUS的eFlash控制器设计-软硬件系统设计

    eFlash软硬件系统设计 软硬件划分 划分好软硬件之后,IP暴露给软件的寄存器和时序如何? 文档体系:详细介绍eflash控制器的设计文档 RTL代码编写:详细介绍eflash控制器的RTL代码 1 ...

  7. 【MACRO】嵌入式实用的宏技巧 DEBUG-printf 、 #/##

    from: C语言.嵌入式中几个非常实用的宏技巧 (qq.com) 宏打印函数 在我们的嵌入式开发中,使用printf打印一些信息是一种常用的调试手段.但是,在打印的信息量比较多的时候,就比较难知道哪 ...

  8. android应用申请加入电池优化白名单

    首先,在 AndroidManifest.xml 文件中配置一下权限: 1 <uses-permission android:name="android.permission.REQU ...

  9. Go-稀疏数组

    package main import "fmt" // 稀疏数组 // 1. 二维数组 // 2. 存在大量相同相同数据和少量不同数据 // 思维: 将大量相同数据转化为: (数 ...

  10. [转帖]mysql8.0 MySQL函数

    PART1. MySQL函数介绍 函数表示对输入参数值返回一个具有特定关系的值,MySQL提供了大量丰富的函数,在进行数据库管理以及数据的查询和操作时将会经常用到各种函数.各类函数从功能方面主要分为数 ...