Python基础(九)--异常
1 异常相关概念
1.1 什么是异常
异常是程序运行过程中产生的一种事件,该事件会打乱程序的正常流程。可以说,异常就是一种意外,指程序没有按照正常或期望的方式执行。
当异常产生时,会创建一个相关异常类的对象,该对象含有异常的相关信息。异常产生时,会在异常的上下文中寻找异常处理程序,如果没有异常处理程序,则异常产生之后的语句将不会得到执行。该异常会向上传播。传播的方式为:
①如果异常是在函数中产生的,则传播给函数的调用者
②如果异常是在模块顶级位置产生的,则传播给导入该模块的另一个模块
如果当前模块作为脚本运行,则异常会传播给解释器,如果到了解释器,异常还未解决,则当前线程会终止执行。离异常产生越近的信息,会在越下面显示,因此分析异常时,应该从下往上看。
def A():
B()
def B():
C()
def C():
a = 6 / 0
A()
1.2 常见异常类型
异常命名惯例,以Error结尾。
异常类型 | 描述 |
BaseException | Python中所有异常的根类 |
Exception | BaseException的子类,用户异常的根类 |
ZeroDivisionError | 当除数为0时,产生的异常 |
AssertionError | 断言的异常,当断言条件不满足时产生 |
ModuleNotFoundError | 当使用import导入的模块不存在时产生的异常 |
IndexError | 索引异常,当索引越界时产生 |
KeyError | 键访问异常,当键不存在时会产生 |
NameError | 名称异常,当访问名称不存在时产生 |
RecursionError | 递归异常,当函数调用层次达到最大层次限制时产生 |
StopIteration | 迭代终止异常,当迭代器已经没有元素是产生 |
SyntaxError | 语法错误异常,编写的代码不符合语法规定时产生 |
ValueError | 值异常,当对值处理错误是产生 |
2 捕获异常
2.1 try...except
在Python中,可以使用try-except的语法来捕获异常,我们可以将其称为异常处理程序。格式如下:
try:
可能产生异常的程序
except 异常类型1:
恢复措施
except 异常类型2:
恢复措施
……
except 异常类型n:
恢复措施
其中,try用来执行可能会产生异常的程序,而except用来捕获try中产生的异常,用来执行一些恢复或补救操作,或者给出错误的提示信息等。这分为三种情况:
①try语句块没有产生异常。
此时try语句块全部执行完毕,不会执行任何except分支(因为没有异常),然后继续执行try-except之后的语句。
②try语句块中产生异常,except捕获了该异常。
会创建相关异常类型的对象,try异常之后的语句不会得到执行(不论except能否捕获该异常)。然后对该异常对象与各个exception分支不会的异常类型进行匹配。匹配的原则为:isinstance(异常对象,except捕获的异常类型),如果匹配成功,则执行对应的except分支,匹配顺序从上到下。如果能够匹配成功,表示成功捕获异常,try-except之后的语句可以正常执行。
③try语句块中产生异常,但是except没有捕获该异常。
如果try中产生异常,但是except没有捕获该异常,则该异常继续存在,try-except之后的语句不会得到执行。该异常向上传播
捕获异常对象:可以使用except捕获异常,同时,我们也能够使用as语法获取try中产生的异常对象。语法格式为:
try:
可能产生异常的代码
except 异常类型 as 变量:
处理异常代码
当异常匹配成功时,我们就会将try中产生的异常对象赋值给as变量名,然后,我们就可以在except中使用变量名来访问异常对象了。
try:
print(5 / 0)
except ZeroDivisionError as e:
# 通过异常对象的args属性获取异常对象构造器的参数。
print(e.args)
异常对象的args属性返回一个元组类型,其中存放异常对象创建时,传递给构造器中的参数值。
2.2 捕获多个一次
因为在try语句块中,可能产生不止一种异常,故我们会使用多个except分支来捕获这些可能产生的异常。如果多个异常类之间没有继承关系时,except分支的顺序不是十分重要,但是,当异常类之间存在异常关系时,就一定要将子类放在前面,父类放在后面。因为子类异常对象也是父类异常类的实例,如果将父类分支放在子类分支之前,则就算try中产生子类异常,也会先由父类分支率先捕获,子类分支永远都没有机会执行,这就失去了意义。所以,在捕获异常时,except分支应该按照从特殊性(子类)到一般性(父类)的方式排序。
except还有一种语法,就是不指定异常类型,此时表示捕获所有异常类型。因为捕获所有的异常类型是最广泛的(最具有一般性),所以,如果使用这种方式,则必须将该except置于最后(作为最后一条except分支)。例如:
try:
可能产生异常的代码
except: # 没有指定异常类型,会捕获所有的异常。
pass
同时捕获多个异常:当try中产生了两种(或更多)的异常,而多种异常的处理方式又完全相同时,我们使用多条except分支,会造成代码的重复。此时,我们可以使用一条except分支,同时捕获多个异常来代替。
try:
# 操作
except (IndexError, KeyError):
print("提供值不合法,获取失败!")
这样,无论try中产生IndexError还是KeyError,except分支都可以进行捕获。也许大家会有这样的想法,这种捕获多个异常有什么用呢,使用不指定异常类型的except岂不是更好,能捕获所有异常,可谓“万事通用”。
try:
# 操作
except:
print("提供值不合法,获取失败!")
但是,如果这样做,就很可能会捕获预期之外的异常,从而掩埋真正的问题,令错误不易排查。因此,如果能够捕获更加具体明确的异常类型,我们最好不要使用更加通用一般的异常类型代替。
2.3 else
try-except还可以跟随可选的else语句。语法为:
try:
……
except 异常类型:
……
else:
……
当try中没有产生异常时,就会执行else分支,否则,不执行else分支。似乎感觉,else分支并没有什么作用,完全可以将else分支放在try语句块的后面啊,因为如果产生异常,try中异常之后的语句也不会执行。如果从程序(计算机)的角度讲,确实可以这样进行修改,然而,如果从程序员的角度讲,else还是具有一定的意义的。因为,其可以将“可能产生异常的代码”与“不产生异常后执行的代码”进行有效的分离,从而让程序更加清晰,具有可读性。
else语句的作用:try中的语句应该是可能产生异常的语句,对于不会产生异常的语句,我们就不适合放在try中。else就可以将try中不产生异常的语句从try中分离,令程序结构变得更加清晰。
2.4 finally
try-except还可以带上一个可选的finally。如果同时存在else,则finally必须处于else的后面,其实,except,else,与finally都是可选的。但是,except与finally二者不能同时缺失,即二者至少要存在一个。
finally会在try-except-else之后得到执行(如果存在except或else的话),且一定会得到执行。即无论try是否产生异常,也无论except是否捕获try中产生的异常,finally终将会得到执行。
考虑到finally总是可以执行的特征,我们往往会在finally中执行一些清理的工作。例如,我们在try中申请了一些系统资源(文件读写,数据库连接等),就可以在finally中进行资源释放,从而不会造成资源泄露(资源申请,但没有释放)。如果将释放语句写在try中,则一旦在释放之前产生异常,则资源释放语句就不会得到执行。
# 在try和finally中同时又return时,finally中的return会镇压掉try中的
def fun():
try:
return 1
finally:
return 2
print(fun())
# 当try语句体中尝试返回一个变量时,如果在finally中去修改该变量的值,
# 不会影响到返回值的结果(返回的还是修改之前的值)
def fun2():
a = 1
try:
return a
finally:
a = 2
print(fun2())
# 验证finally是否总会执行
# 1在循环中,通过break尝试跳过finally语句块
for i in range(10):
try:
break
finally:
print("finally总会执行")
# 2、在方法中,执行return尝试跳过finally语句块。
def f():
try:
return
finally:
print("finally总会执行")
f()
# 3、调用sys模块的exit方法,尝试跳过finally语句块
import sys
try:
sys.exit()
finally:
print("finally总会执行")
3 抛出异常
在不符合要求时,可以主动产生一个异常,这样就能够给客户端一个有效的提醒。我们可以创建一个异常对象,使用raise抛出,语法为:
raise 异常类或异常对象
例如:raise Exception("产生异常")
raise Exception
raise后面跟随异常类,则相当于是调用其无参的构造器,因此,后者相当于:
raise Exception()
raise后面必须是一个有效的异常类(BaseException类型或其子类型)或对应异常类的对象,如果是其他类型,将会产生错误。
4 自定义异常
自定义异常通常继承Exception类型(或其子类型),按照惯例,异常类以Error结尾。
为什么需要自定义:①Python提供的异常类型不能满足所有的用户,所有场景的需要。②自定义异常可以与Python系统提供的异常类型分开,有利于定位并解决问题