python程序设计第16课第七章异常

第七章 异常

1.异常概述

异常(Exception)一般是指程序运行而不是编码时引发的错误,引发的错误有很多。之前我们已经见到了很多异常情况,比如如下几个例子

在这里插入图片描述

第一个错误是因为在解释环境内键入一个没有定义的变量名,就会抛出python内置,提前定义的异常类名NameError。命名的方式实际上用到我们之前讲过的规范,两个首字母都是大写提示我们这是一个类名第二个错误是因为除数为0,抛出提前定义的ZeroDivisionError,第三个,第四个错误中数字加字符串和字符串加数字都会产生TypeError。

要注意在python默认的解释环境里在出现了异常之后的显示格式是python定义的。异常名之后还会有短的字符串来进行解释。你自己定的异常类最好也要这样加上解释性的字符串

默认异常处理器

异常发生后如果我们的代码没有刻意捕捉这个异常,它就会一直向上传递到程序顶层并启动默认的异常处理器:也就是打印标准出错消息并终止程序的执行。下面这个例子在交互环境里运行,所谓的顶层就是这个交互环境本身

在这里插入图片描述

自己捕获异常

很多时候我们不希望出现异常之后就立即终止程序的执行。如果不想要默认的异常处理行为,就可以把可能会产生异常的代码包裹在try-except之间,然后在except之后写上可能会发生的异常类的名字。在这种情况下如果发生了提前预料的错误就会执行except后的内容。这样python就会使用你的异常处理逻辑而不是终止程序打印错误信息

针对上一个例子中fetch(x,4)产生的异常编写try-except语句来处理异常,例子如下

>>>try:
...    fetch(x,4)
...except IndexError:
...    print('got exception!')
...
got exception!
>>>

>>>def catch():
...try:
...    fetch(x,4)
...except IndexError:
...    print('got exception!')
...print('continuing...')
...
>>>catch()
got exception!
continuing  #可见产生异常并被捕捉后,程序仍然正确运行下去而不是终止执行
>>>

自己引发异常

除了让python通过生成错误来引发异常,我们也可以使用raise语句来自己引发异常。

>>>try:
...    raise IndexError
...except:
...    print('got exception!')
...
got exception!
>>>

自己定义异常

之前的IndexError是python的一个内置异常,用户也可以自己定义新的异常。用户定义的异常通过类编写,它继承自python的一个内置的异常基类,通常是Exception。下面这个例子中即使Bad这个类里什么都不做也可以当作一个异常类来使用。定义的函数doomed内手动触发Bad异常。raise Bad()表示抛出Bad异常类的一个实例。既然知道会抛出Bad这个异常,就可以明确的写出要捕捉Bad类异常并添加自己定义的异常处理逻辑

在这里插入图片描述

2.异常代码细节

try…except…else语句

模板如下:

try:
    <statements> # Run this main action first
except <name1>:
    <statements> # Run if name1 is raised during try block
except (<name2>, <name3>):
    <statements> # Run if any of name2 or name3 occur
except <name4> as <instance>:
    <statements> # Run if name4 is raised, and get instance raised
except:
    <statements> # Run for all (other) exceptions raised
else:
    <statements> # Run if no exception was raised during try block

#else分句只有在try代码块执行不发生异常时才会被执行

#倒数第四行中空的except分句会会捕捉try代码块执行时所发生的所有异常。

想要"捕捉一切"的分句,空的except就能做到。

try:
    action()
except: #catch all possible exceptions

但是空的except会捕捉一些和程序代码无关的系统异常。例如在Python中,即便是系统离开调用,也会触发异常,而你通常会想让这些事件通过而不是拦截它们。之所以会有这种现象是因为python内置异常类的继承层次中Exception类与系统退出事件类是平级的,都是BaseException的子类

在这里插入图片描述

考虑到这个原因可以改进一下代码为捕捉一个名为 Exception 的异常。它的效果和空的except语句类似,但是能忽略和系统退出相关的异常(必考)

try:
    action()
except Exception: #Catch all possible exceptions,except exits
    ...

try…finally语句

模板如下

try:
<statements> # Run this main action first
finally:
<statements> # Always run this code snippet no matter
             # an exception happens or not

Python会先执行 try 之下的代码块
• 如果 try 代码块运行时没有发生异常,Python会在try代码块执行完后继续执行 finally 代码块
• 如果 try 代码块运行时有异常发生,Python会运行 finally 代码块,接着把异常向上传递到较高的 try 语句或顶层的默认异常处理器

因为无论是否发生异常,finally内的语句都会被执行,因此可以把一些一定要执行的代码放在finally的后面。 比如在实际应用中可以把清理动作如关闭文件,断开服务器连接等放在 finally 子句中

示例如下

在这里插入图片描述

try…except…finally语句

try:
   <statements>
except <name1>:
   <statements>
except <name2>:
   <statements>
...
else:
    <statements>
finally:
    <statements>

try后紧跟的代码块首先被执行。如果在执行期间引发异常则试图匹配异常,寻找与抛出异常相符的 except 子句。如果没有引发异常则执行 else 子句的代码块,而无论如何finally代码块都将被执行

raise语句

raise语句用于显示地触发异常

在这里插入图片描述

这两种形式是相等的,都会引发指定的异常类的一个实例

raise 语句不包括异常名称时就是重新引发当前的异常。如果要捕捉和处理一个异常又希望这个异常被向上抛出就可以使用单独的 raise 语句

在这里插入图片描述

自己定义异常类

示例:

在这里插入图片描述

要自己定义一个异常类一般要继承Exception基类,这里General就是自己定义的异常类,还可以用自己定义的异常类再次派生子类,如示例中的Specific1和Specific2。

注意示例下半部分的

for func in (raiser0,raiser1,raiser2):
    try:
        func()
    except General:
        print('caught',sys.exc_info())
 #依次抛出父类General异常和两个子类Specific1和Specific2。但是在抛出异常后使用一个except General尝试去捕捉它然后去打印它的信息。通过输出信息可以发现except general既可以捕捉到父类的General也可以捕捉到子类的,而且在打印信息的时候都可以把相关的异常类的名字准确的打印出来。
#因此可以借助异常之间的继承关系仅仅去捕捉父类,通过这种方式可以把该继承树上的所有的异常类一网打尽。这也回答了为什么用Exception就可以捕捉到所有和系统调用无关的异常,因为仅仅去捕捉它们的父类,就可以一次性去匹配所有该父类的子类

打印信息

抛出异常时可以附加提示信息,这对内置异常类和用户自定义的异常类都适用

示例

在这里插入图片描述

把提示字符串直接放在异常类名字的后面。加括号后就感觉由这个异常类生成了一个实例一样。示例左边代码中异常被打印出来之后,它就会把spam也作为一个变量返回出来。看示例右边在捕捉到Bad异常后可以用except Bad as x这种语法形式获得一个异常的实例x。打印的时候print x就会把我们提前赋给它的字符串打印出来。

慎用空except子句和except Exception

捕捉的涵盖面不要太广。空except子句和except Exception虽然方便,但是可能拦截到异常嵌套结构中较高层的 try 所期待的异常。而且有时会更糟,可能会捕捉与应用程序无关的系统异常。这些异常因为关闭了Python的错误报告机制通常不应该被拦截。所以要慎用空except子句和except Exception,尽量让捕捉的异常具体化,避免拦截无关的异常。那些没有预料的异常,应该给予其机会向上抛直到没有人处理导致它被抛到顶层,这样反倒可以提醒你关注那些可能没有想到的错误和异常。而不是在底层就将其拦截

示例:

def func():
    try:
        ...                  
    except Exception:        
        ...
try:
    func()
except IndexError:         
    ...
#定义的func,内部有try-except。它的try语句块内部可能发生IndexError,如果发生了IndexError会被紧跟着的 except Exception捕捉。现在在外部调用func,这个时候我知道IndexError可能会发生IndexError异常,所以采用try-except尝试捕捉IndexError。但是这种写法是不起作用的,因为所有异常在func内部已经被捕捉,造成IndexError没有机会再往上一层传播,底下这部分实际上是没有用的代码

3.断言

在写程序的时候可以多去利用断言,然后在程序执行的时候可以通过一定的机制把所有的断言语句关闭掉或者把它们全部从最终执行的代码里移掉。这样能提高程序执行的效率

断言(assert)的语法

assert<test>[,<arguments>]
#assert本身不是一个函数
#如果test表达式为真就什么都不做,如果test为假则抛出异常。参数arguments是可选项,如果提供就是赋给异常的额外数据

相当于如下代码

if __debug__:
    if not <test>:
        raise AssertionError(<arguments>)
#assert实际上和名为__debug__的python内置变量有关。__debug__为True的时候,assert才会启动。从__debug__的名字就可以看出它与调试相关。当程序开发好的时候可以把__debug__变量变为False,这样assert就会完全不起作用

示例:

在这里插入图片描述

写assert False就会向上抛出异常AssertionError。后面还可以加上附加信息(一般是字符串作为参数),打印AssertionError的时候会把这些附加信息打印在后面。

assert语句的用处:

assert 语句一般用于开发时对特定必须满足的条件进行验证,仅当 __ debug __ 为 True 时有效。开发的时候可以手动把 __ debug __ 改为False

当Python脚本以-O选项执行时assert机制会被关闭,assert语句的 __ debug __ 就会被手动改为False,然后从程序编译后的字节码中移除,从而优化程序,提高运行速度

示例:

在这里插入图片描述

4.with…as 环境管理器

with … as 可作为 try … finally 的替代方案。常用于定义必须执行的终止或清理行为,无论前期的处理步骤中是否发生异常(产生异常后程序可能崩溃或者终止执行,在终止执行之前很可能没有机会去执行一些终止或清理的行为),终止和清理行为都将被执行。其最简单的用途就是打开文件后不管是否发生异常最后都把它关上以防止内存泄露

with…as语法

with <expression> [as <variable>]
<statement>

示例:

下面两块代码的作用相同。为了无论什么情况下关闭文件的操作总有机会执行,可以把对文件的关闭放在finally后面。在python里还有一种更简洁的方法,直接在with open(‘filename’) as f:代码块里面遍历f,这样就不用把f.close显示地写出来。以后在自己处理文件的时候都应该采用这种写法

with open('filename') as f:
    for line in f:
        print(line)
        ... more code here ...
        
f = open('filename')
try:
    for line in f:
        print(line)
        ... more code here ...
finally:
    f.close()

写出来。以后在自己处理文件的时候都应该采用这种写法

with open('filename') as f:
    for line in f:
        print(line)
        ... more code here ...
        
f = open('filename')
try:
    for line in f:
        print(line)
        ... more code here ...
finally:
    f.close()

编辑于2020-5-26 23:17

猜你喜欢

转载自blog.csdn.net/zimuzi2019/article/details/106638930