欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能!
前言
在Python中,异常是一种特定的对象,能够在程序运行过程中被抛出和处理。有效地管理异常不仅可以增强程序的稳定性,还可以提高用户体验,使程序能够优雅地处理错误情况。本章详细讲解了异常的基本概念、如何捕获和处理异常以及异常的传递性。
本篇文章参考:黑马程序员
一、什么是异常
当检测到一个错误时,Python解释器无法继续执行程序,反而会抛出错误提示,这就是我们所称的“异常”,也就是常说的“bug”。
那bug
这个单词是怎么诞生的呢?
"bug"这个词最初的确是指“虫子”。在英语中,"bug"可以用来描述各种小昆虫。早期计算机采用大量继电器工作,马克二型计算机出现了故障,技术人员尝试了多种方法,最后定位到第70号继电器出错。负责人哈珀仔细观察这个出错的继电器,发现一只飞蛾躺在中间,已经被继电器打死。她小心翼翼地用镊子将这只蛾子取出,将其用透明胶带粘贴在“事件记录本”上,并注明为“第一个发现虫子的实例”。自此之后,导致软件故障的缺陷便被称为“bug”。
# 打开一个不存在的文件
f=open("D:/test.txt","r",encoding="UTF-8")
运行结果:
控制台打印出的错误信息通常表示在程序运行过程中遇到的异常(Exception)。当程序遇到无法处理的错误时,就会抛出异常,并在控制台输出相关的错误信息,包括异常类型、错误描述以及错误发生的位置。
二、捕获异常
为什么要捕获异常呢?
世界上没有完美的程序,任何程序在运行的过程中,都有可能出现异常,也就是出现bug,导致程序无法完美运行下去。
我们要做的,不是力求程序完美运行。而是在力所能及的范围内,对可能出现的bug,进行提前准备、提前处理。这种行为我们称之为异常处理(即捕获异常)。
当我们的程序遇到了Bug, 有以下两种情况:
- 整个程序因一个Bug停止运行。
- 程序能够提醒用户Bug的发生,并继续正常运行。
在实际工作中,我们肯定不能因为一个小小的Bug就让整个程序全部奔溃,所以我们希望的是达到第二种情况。为此,我们需要使用异常捕获技术。
捕获异常的作用:提前预测某个地方可能会出现异常,并做好相应的准备。当实际发生异常时,我们可以采取后续措施来处理这些异常。
①捕获常规异常
基本语法:
try:
可能引发异常的代码
except:
如果出现异常执行的代码
# 捕获异常
try:
# 打开一个不存在的文件
f=open("D:/test.txt","r",encoding="UTF-8")
except:
print("出现异常了,因为文件不存在,改为w模式打开")
# w模式:当文件不存在时会创建一个文件
f=open("D:/test.txt","w",encoding="UTF-8")
输出结果:
出现异常了,因为文件不存在,改为w模式打开
②捕获指定异常
基本语法:
try:
可能引发异常的代码
except SpecificException as e:
处理特定异常的代码
# 捕获指定异常
try:
print(name)
# 捕获 NameError 异常,并将异常对象赋值给变量 'e'
except NameError as e:
# 输出提示信息
print("出现变量未定义异常")
# 输出异常对象 'e' 的信息
print(e)
输出结果:
出现变量未定义异常
name ‘name’ is not defined
如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
# 捕获指定异常
try:
1/0
except NameError as e:
print("出现变量未定义异常")
print(e)
运行结果:
③捕获多个异常
当捕获多个异常时,将要捕获的异常类型的名字放到except 后,并使用元组的方式进行书写。
# 捕获多个异常
try:
1/0
except (NameError,ZeroDivisionError) as e:
print("出现变量未定义或者除以0的异常")
print(e)
输出结果:
出现变量未定义或者除以0的异常
division by zero
# 捕获多个异常
try:
print(name)
except (NameError,ZeroDivisionError) as e:
print("出现变量未定义或者除以0的异常")
print(e)
输出结果:
出现变量未定义或者除以0的异常
name ‘name’ is not defined
# 捕获多个异常
try:
print(name)
1/0
except (NameError,ZeroDivisionError) as e:
print("出现变量未定义或者除以0的异常")
print(e)
输出结果:
出现变量未定义或者除以0的异常
name ‘name’ is not defined
仔细观察这个输出结果,为什么会输出name ‘name’ is not defined但是不会输出division by zero呢?
这是因为在Python中捕获多个异常时,try 块中的代码是自上而下执行的,一旦遇到异常,程序会立刻跳转到相应的 except 块,后续的代码将不再执行。
这段代码中,print(name) 这行会首先执行,然而 name 变量并不存在,所以会首先引发NameError
异常并立即跳转到对应的 except 块来处理这个异常,而不会继续执行 try 块中的后续代码。因此,1/0 这行代码并不会执行,所以不会引发 ZeroDivisionError
异常。
④捕获所有异常
基本语法1:
try:
可能引发异常的代码
except:
如果出现异常执行的代码
基本语法2:
try:
可能引发异常的代码
except Exception as e:
如果出现异常执行的代码
# 捕获所有异常
# 写法一:(这种写法较为常用)
try:
print(name)
except Exception as e:
print("出现异常了")
# 写法二:
try:
1/0
except:
print("出现异常了")
输出结果:
出现异常了
出现异常了
⑤异常else
else表示的是如果没有异常要执行的代码。
try:
print("Hello")
except Exception as e:
print("出现异常了")
else:
print("没有出现异常")
输出结果:
Hello
没有出现异常
⑥异常finally
finally表示的是无论是否异常都要执行的代码。
try:
print("Hello")
except Exception as e:
print("出现异常了")
else:
print("没有出现异常")
finally:
print("我是finally,有没有异常我都会执行")
输出结果:
Hello
没有出现异常
我是finally,有没有异常我都会执行
try:
1/0
except Exception as e:
print("出现异常了")
else:
print("没有出现异常")
finally:
print("我是finally,有没有异常我都会执行")
输出结果:
出现异常了
我是finally,有没有异常我都会执行
三、异常的传递
异常是具有传递性的。
# 定义一个出现异常的方法
def func1():
print("func1 开始执行")
num = 1 / 0 # 除以0的异常
print("func1 结束执行")
# 定义一个无异常的方法,调用上面的方法
def func2():
print("func2 开始执行")
func1()
print("func2 结束执行")
# 定义一个方法,调用上面的方法
def main():
try:
func2()
except Exception as e:
print(f"出现异常了,异常的信息是:{e}")
main()
输出结果:
func2 开始执行
func1 开始执行
出现异常了,异常的信息是:division by zero
【分析】
当函数func01中发生异常, 并且没有捕获处理这个异常的时候, 异常会传递到函数func02,;当func02也没有捕获处理这个异常的时候,异常会传递到main函数;最终,main函数捕获了这个异常, 这就是异常的传递性。
注意:如果函数都没有捕获异常, 程序就会报错。
# 定义一个出现异常的方法
def func1():
print("func1 开始执行")
num = 1 / 0 # 除以0的异常
print("func1 结束执行")
# 定义一个无异常的方法,调用上面的方法
def func2():
print("func2 开始执行")
func1()
print("func2 结束执行")
# 定义一个方法,调用上面的方法
def main():
func2()
main()
运行结果:
利用异常具有传递性的特点,在main函数中设置异常捕获便可保证程序不会因为异常崩溃。因为整个程序无论在哪里发生异常,异常最终都会传递到main函数中,进而确保所有的异常都会被捕获。