Python3基础篇(十)——异常处理
Python3基础篇(十)——异常处理
前言:
阅读这篇文章我能学到什么?
这篇文章将为你介绍Python3中的异常捕获和处理,如果你看过《代码大全2》会明白为程序设计上异常的处理是多么重要的一件事。如果你希望对它有一些基础的了解,那么请读这篇文章。
1 程序异常处理
程序异常就是程序的运行结果超出了设计者的预料,程序的运行是“非正常”的执行流程。程序的异常处理其实应该分两个阶段,第一个阶段是异常的检测(识别出异常状态,并区分出是何种异常),第二个阶段是针对特定异常情况应该做何种处理(处理可以是忽略、修正、甚至重启)。变成语言支持异常处理已经不是什么“新鲜”的事了,但还是要提一下早期程序处理异常是用 error code 的方式,即函数或代码段返回故障码,通过故障码来区分异常种类和决定如何处理。这种方式已经日渐淘汰,现在很多编程语言已经对异常处理有了较好的支持,形式通常是 try-catch ,在Python3中是 try-except形式。断言是一种常用的异常处理,它一般用于调试阶段(发行版一般将其关闭)。
1.1 assert(断言)
也其他语言的断言处理一样,当断言的条件为False
时,触发异常进行断言处理。断言用于在程序检测到异常时立即终止程序的运行并抛出此时的异常,不必等到后续运行到程序崩溃,抛出一大堆非根源错误信息。代码大全的防御式编程思想是建议在开发调试阶段使问题尽可能“扩大化”,让小问题也无法被忽视(小问题也导致程序运行终止,这样有利于我们写出健康强壮的代码)。
我们尝试实现一个功能函数,为它加上断言来捕获一些异常。
代码示例:
1 | def Myabs(Num): #求数的绝对值 |
运行结果:
1 | 1 |
这个函数用于求一个数的绝对值,看上去似乎没有问题,因为我们进行了一些简单测试后发现结果符合我们的预期。但是如果这个代码交到客户手中将会出现严重的bug,因为你无法现象客户可能给这个函数传递什么奇葩的内容进去。
进行如下测试:
1 | def Myabs(Num): #求数的绝对值 |
这几种非法输入全部都将报错,因为这个功能函数异常很可能导致最后整个程序运行异常。我们需要对输入进行一些限制,现在在讲断言,我们就尝试用断言进行这些异常处理(假设我希望非法输入时程序立即停止运行并告诉我发生了什么异常,不要在非法输出的情况下进行后续处理)。
assert的用法非常简单:
语法结构:
1 | assert expression |
当expression
结果为False
时触发断言,它将立即终止程序运行并抛出异常信息。
代码示例:
1 | def Myabs(Num): #求数的绝对值 |
运行结果:
1 | 1 |
可以看到编译器其实也检测到类型问题抛出错误信息了,这种错误不是语法错误,编译器不能在编译的时候检查出错误,只能在运行的时候。也不能指望测试的时候能100%覆盖到所有输入可能(当然我这里举的例子很简单,稍微有点经验的程序员都能想到非法输入的异常),加入异常就是为了处理发生预料之外的情况。断言在异常时停止程序运行,使得在小的“问题”也无法被忽略,提醒程序设计者“这里”有“超出预料”发生。可以看到异常信息assert isinstance(Num, int) or isinstance(Num, float)
,提示我们是程序什么地方在抛出这个异常。
1.2 try异常捕获和处理
Python3提供了较为良好的异常处理机制。下面具体介绍每种语法结构的用法。
1.2.1 try-except结构
语法结构:
1 | try: |
try
代码块运行时会监视是否有异常事件抛出(可以是Python3自动抛出的异常,也可以由设计者主动抛出异常)。如果检测到异常抛出会执行except
后面的代码块,没有异常则忽略这部分代码。
还需要注意的是当try
中运行到抛出异常位置时,将不会继续执行后续的代码。except
后可以接具体的异常类型,一个 try-except结构可以有多个except
,一个except
后可以写多个异常(要以元组形式写出)。当except
后不写异常类型时则表示针对所有异常类型。
代码示例:
1 | def Function(Num1, Num2): |
运行结果:
1 | Here |
Num2为0时Python3会自动抛出异常,这个异常被捕获并执行except
后(没有指明针对何种异常类型,是针对所有异常类型)的代码快处理异常。
Num2为0进行除法的异常是Python3自己抛出的,我们尝试主动抛出异常。抛出异常需要使用关键字raise
,后面接抛出的异常类型。
代码示例:
1 | def Function(Num1, Num2): |
运行结果:
1 | ZeroDivisionError |
当try中抛出异常时停止try
代码块的执行,开始在except
语句中查找能匹配抛出异常的分支(就像if-elif语句那样从上往下找到满足条件的分支执行里面的代码块),找到后就执行相应except
后面的代码块。没有写明异常类型的except
分支是针对所有异常类型。raise
后接需要抛出的异常类型,raise
后面的代码不会被执行,因为异常已经抛出了。
给一个except
分支指定多个异常类型。
代码示例:
1 | def Function(Choose): |
运行结果:
1 | error |
ValueError
和ZeroDivisionError
都是Python3的内置异常类型,也可以自己定义异常类型。关于异常类型放在这篇文章的后面讲。
1.2.2 try-except-else结构
try-except 结构基础上可以继续添加else
分支,当try中没有抛出异常时将会执行else
后的代码块,若有任何异常抛出则不会执行。注意,except
分支和else
分支并不是一定“互斥”执行的,若try中抛出异常但是except
找不到该类异常匹配的分支,则既不会执行except
也不会执行else
后的代码块。
语法结构:
1 | try: |
代码示例:
1 | def Function(Choose): |
运行结果:
1 | No error |
else
分支就是无异常时要做什么处理。
1.2.3 try-except-finally结构
finally
分支不论是否有异常抛出都会被执行。 try-except-finally 结构也可以结合上else
分支变成 try-except-else-finally 结构。
语法结构:
1 | try: |
或者
1 | try: |
代码示例:
1 | def Function(Choose): |
运行结果:
1 | error |
当有异常时如果except
分支里具有该类异常的匹配项,则执行该分支,没有则不执行except
分支,但是一定会执行finally
分支。当没有异常时,出了执行else
分支还要执行finally
分支。所以含有finally
的异常结构可能有两个分支都被执行。
2 异常结构的嵌套
与 if-elif 结构类似的,异常结构也能进行嵌套。嵌套时外层异常结构可以将内层异常结构看做代码块,当外层try代码块内的内层异常结构,有异常抛出时(指内层处理不了把异常往外层抛出),会被外层的try捕获,然后在except
中寻找匹配的分支执行。当内层能处理自己的异常时,异常不被抛到外层,对外层try
来说就是没有异常发生。异常嵌套的执行规则和非嵌套结构一样,把内层异常结构看做代码块就可以了。
代码示例:
1 | def Function(Choose): |
运行结果:
1 | Deal ValueError |
在嵌套异常结构类,内层循环不能处理(except
没有匹配的分支)的异常会继续抛出到外层,抛出异常后内层循环的代码会停止往后继续执行。总之就是考虑外层时,把内层看作代码块去理解。
内层也可以主动将异常抛出给外层处理。
代码示例:
1 | def Function(Choose): |
运行结果:
1 | raise ValueError |
3 异常类型
上面涉及到了两种(ValueError
和ZeroDivisionError
)Python3内置的异常类型,Python3提供了很多内置的异常类型,比如:
异常类型 | 含义 |
---|---|
SyntaxError | 语法错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
OverflowError | 数值运算超出最大限制 |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
等等,太多了不一一列举。这些异常种类是Python3为我们定义好的,设计者可以自己定义属于自己的异常类。定义异常类需要继承Exception
异常基类,自定义的异常类可以继续被继承,它们依然是异常类。
打码示例:
1 | import sys |
运行结果:
1 | MyInputException --File: C:/Users/think/Desktop/Python_Test/Test.py, --FunName: Function --Line: 20, --Msg: Error Test |
MyInputException
和MyOutputException
自定义异常类继承于自定义异常类MyException
,总之自定义异常类必须直接或间接继承于Exception
类,这是Python3内置的异常基类。as
关键字给异常类型取了别名,方便通过别名(对象)访问自定义类的成员变量或方法(比如E.Msg
)。