Python基础(九)--异常

                                      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系统提供的异常类型分开,有利于定位并解决问题

 

发布了76 篇原创文章 · 获赞 9 · 访问量 5472

猜你喜欢

转载自blog.csdn.net/weixin_43786255/article/details/103101896
今日推荐