什么是异常处理,Python常见异常类型

编写程序时遇到的错误可大致分为 2 类,分别为语法错误和运行时错误。

Python语法错误

语法错误,也就是解析代码时出现的错误。当代码不符合 Python 语法规则时,Python解释器在解析时就会报出 SyntaxError 语法错误,与此同时还会明确指出最早探测到错误的语句。

Python运行时错误

运行时错误,即程序在语法上都是正确的,但在运行时发生了错误。
在 Python 中,把这种运行时产生错误的情况叫做异常

Python异常处理机制

使用 Python 的异常处理机制就可以解决这个问题

1
2
3
4
5
6
7
try:
if(用户输入不合理):
raise 异常
except Exception:
alert 输入不合法
goto retry
#正常的业务代码

此程序中,通过在 try 块中判断用户的输入数据是否合理,如果不合理,程序受 raise 的影响会进行到 except 代码块,对用户的错误输出进行处理,然后会继续执行正常的业务代码;反之,如果用户输入合理,那么程序将直接执行正常的业务代码。
try except 是 Python 实现异常处理机制的核心结构

Python try except异常处理详解

Python 中,用try except语句块捕获并处理异常
语法结构:

try:
可能产生异常的代码块
except [ (Error1, Error2, … ) [as e] ]:
处理异常的代码块1
except [ (Error3, Error4, … ) [as e] ]:
处理异常的代码块2
except [Exception]:
处理其它异常

该格式中,[] 括起来的部分可以使用,也可以省略。其中各部分的含义如下:
(Error1, Error2,…) 、(Error3, Error4,…):其中,Error1、Error2、Error3 和 Error4 都是具体的异常类型。显然,一个 except 块可以同时处理多种异常。
[as e]:作为可选参数,表示给异常类型起一个别名 e,这样做的好处是方便在 except 块中调用异常类型
[Exception]:作为可选参数,可以代指程序可能发生的所有异常情况,其通常用在最后一个 except 块。

try 块有且仅有一个,但 except 代码块可以有多个,且每个 except 块都可以同时处理多种异常

try except 语句的执行流程如下:
首先执行 try 中的代码块,如果执行过程中出现异常,系统会自动生成一个异常类型,并将该异常提交给 Python 解释器,此过程称为捕获异常。
当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为处理异常。

如果 Python 解释器找不到处理异常的 except 块,则程序运行终止,Python 解释器也将退出

事实上,不管程序代码块是否处于 try 块中,甚至包括 except 块中的代码,只要执行该代码块时出现了异常,系统都会自动生成对应类型的异常。
但是,如果此段程序没有用 try 包裹,又或者没有为该异常配置处理它的 except 块,则 Python 解释器将无法处理,程序就会停止运行;反之,如果程序发生的异常经 try 捕获并由 except 处理完成,则程序可以继续执行。

1
2
3
4
5
6
7
8
9
10
try:
a = int(input("输入被除数:"))
b = int(input("输入除数:"))
c = a / b
print("您输入的两个数相除的结果是:", c )
except (ValueError, ArithmeticError):
print("程序发生了数字格式异常、算术异常之一")
except :
print("未知异常")
print("程序继续运行")

上面程序中,使用了(ValueError, ArithmeticError)来指定所捕获的异常类型,这就表明该 except 块可以同时捕获这 2 种类型的异常;
只有 except 关键字,并未指定具体要捕获的异常类型,这种省略异常类的 except 语句也是合法的,它表示可捕获所有类型的异常,一般会作为异常捕获的最后一个 except 块。

获取特定异常的有关信息

其实,每种异常类型都提供了如下几个属性和方法,通过调用它们,就可以获取当前处理异常类型的相关信息:

args:返回异常的错误编号和描述字符串;

str(e):返回异常信息,但不包括异常信息的类型;

repr(e):返回较全的异常信息,包括异常信息的类型。

1
2
3
4
5
6
7
try:
1/0
except Exception as e:
# 访问异常的错误编号和详细信息
print(e.args)
print(str(e))
print(repr(e))

除此之外,如果想要更加详细的异常信息,可以使用 traceback 模块

从程序中可以看到,由于 except 可能接收多种异常,因此为了操作方便,可以直接给每一个进入到此 except 块的异常,起一个统一的别名 e。

Python异常处理机制的底层实现

try except异常处理的用法,简单来说,当位于 try 块中的程序执行出现异常时,会将该种异常捕获,同时找到对应的 except 块处理该异常,那么这里就有一个问题,它是如何找到对应的 except 块的呢?

我们知道,一个 try 块也可以对应多个 except 块,一个 except 块可以同时处理多种异常。如果我们想使用一个 except 块处理所有异常,就可以这样写

try:
#…
except Exception:
#…

try 块中可能出现的任何异常,Python 解释器都会交给仅有的这个
except 块处理,因为它的参数是 Exception,表示可以接收任何类型的异常。
注意,对于可以接收任何异常的 except 来说,其后可以跟 Exception,也可以不跟任何参数,但表示的含义都是一样的。

详细介绍一下 Exception。要知道,为了表示程序中可能出现的各种异常,Python 提供了大量的异常类,这些异常类之间有严格的继承关系,图显示了 Python 的常见异常类之间的继承关系。

BaseException 是 Python 中所有异常类的基类,但对于我们来说,最主要的是 Exception 类,因为程序中可能出现的各种异常,都继承自 Exception。

如果用户要实现自定义异常,不应该继承 BaseException ,而应该继承 Exception 类。

关于如何自定义一个异常类

当 try 块捕获到异常对象后,Python 解释器会拿这个异常类型依次和各个 except 块指定的异常类进行比较,如果捕获到的这个异常类,和某个 except 块后的异常类一样,又或者是该异常类的子类,那么 Python 解释器就会调用这个 except 块来处理异常;反之,Python 解释器会继续比较,直到和最后一个 except 比较完,如果没有比对成功,则证明该异常无法处理。

1
2
3
4
5
6
7
8
9
10
try:
a = int(input("输入 a:"))
b = int(input("输入 b:"))
print( a/b )
except ValueError:
print("数值错误:程序只能接收整数参数")
except ArithmeticError:
print("算术错误")
except Exception:
print("未知异常")

该程序中,根据用户输入 a 和 b 值的不同,可能会导致 ValueError、ArithmeticError 异常:

如果用户输入的 a 或者 b 是其他字符,而不是数字,会发生 ValueError 异常,try 块会捕获到该类型异常,同时 Python 解释器会调用第一个 except 块处理异常;

如果用户输入的 a 和 b 是数字,但 b 的值为 0,由于在进行除法运算时除数不能为 0,因此会发生 ArithmeticError 异常,try 块会捕获该异常,同时 Python 解释器会调用第二个 except 块处理异常;
当然,程序运行过程中,还可能由于其他因素出现异常,try 块都可以捕获,同时 Python 会调用最后一个 except 块来处理。
当一个 try 块配有多个 except 块时,这些 except 块应遵循这样一个排序规则,即可处理全部异常的 except 块(参数为 Exception,也可以什么都不写)要放到所有 except 块的后面,且所有父类异常的 except 块要放到子类异常的 except 块的后面。

Python try except else

在原本的try except结构的基础上,Python 异常处理机制还提供了一个 else 块,也就是原有 try except 语句的基础上再添加一个 else 块,即try except else结构。

使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;
反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。

1
2
3
4
5
6
7
8
9
10
try:
result = 20 / int(input('请输入除数:'))
print(result)
except ValueError:
print('必须输入整数')
except ArithmeticError:
print('算术错误,除数不能为 0')
else:
print('没有出现异常')
print("继续执行")

当我们输入正确的数据时,try 块中的程序正常执行,Python 解释器执行完 try 块中的程序之后,会继续执行 else 块中的程序,继而执行后续的程序。

Python try except finally:资源回收

Python 异常处理机制还提供了一个 finally 语句,通常用来为 try 块中的程序做扫尾清理工作。注意,和 else 语句不同,finally 只要求和 try 搭配使用,而至于该结构中是否包含 except 以及 else,对于 finally 不是必须的else 必须和 try except 搭配使用
在整个异常处理机制中,finally 语句的功能是:无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的代码块

基于 finally 语句的这种特性,在某些情况下,当 try 块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally 块中。

1
2
3
4
5
6
7
8
9
try:
a = int(input("请输入 a 的值:"))
print(20/a)
except:
print("发生异常!")
else:
print("执行 else 块中的代码")
finally :
print("执行 finally 块中的代码")

总结
在整个异常处理结构中,只有 try 块是必需的,也就是说:
如果没有 try 块,则不能有后面的 except 块、else 块和 finally 块。

但是也不能只使用 try 块,要么使用 try except 结构,要么使用 try finally 结构;except 块、else 块、finally 块都是可选的,当然也可以同时出现;

可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面;多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。要使用 else 块,其前面必须包含 try 和 except。

另外在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句,一旦在 finally 块中使用了 return 或 raise 语句,将会导致 try 块、except 块中的 return、raise 语句失效

同样,如果 Python 程序在执行 try 块、except 块包含有 return 或 raise 语句,则 Python 解释器执行到该语句时,会先去查找 finally 块,如果没有 finally 块,程序才会立即执行 return 或 raise 语句;

反之,如果找到 finally 块,系统立即开始执行 finally 块,只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except 块里的 return 或 raise 语句。
但是,如果在 finally 块里也使用了 return 或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不会跳回去执行 try 块、except 块里的任何代码。

Python raise用法

Python 允许我们在程序中手动设置异常,使用 raise 语句即可。

raise 语句的基本语法格式为:raise [exceptionName [(reason)]]
其中,用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。

也就是说,raise 语句有如下三种常用的用法:
raise:单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。

raise 异常类名称:raise 后带一个异常类名称,表示引发执行类型的异常。
raise 异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息。

1
2
3
4
5
6
7
try:
a = input("输入一个数:")
#判断用户输入的是否为数字
if(not a.isdigit()):
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))

当然,我们手动让程序引发异常,很多时候并不是为了让其崩溃。事实上,raise 语句引发的异常通常用 try except(else finally)异常处理结构来捕获并进行处理。例如

1
2
3
4
5
6
7
try:
a = input("输入一个数:")
#判断用户输入的是否为数字
if(not a.isdigit()):
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))

raise 不需要参数
正如前面所看到的,在使用 raise 语句时可以不带参数,

1
2
3
4
5
6
7
try:
a = input("输入一个数:")
if(not a.isdigit()):
raise
except RuntimeError as e:
print("引发异常:",repr(e))

Python sys.exc_info()方法:获取异常信息

捕获异常时,有 2 种方式可获得更多的异常信息,分别是:
使用 sys 模块中的 exc_info 方法

使用 traceback 模块中的相关函数

模块 sys 中,有两个方法可以返回异常的全部信息,分别是 exc_info() 和 last_traceback(),这两个函数有相同的功能和用法

exc_info() 方法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别为 type、value 和 traceback,它们的含义分别是:

type:异常类型的名称
value:捕获到的异常实例
traceback:是一个 traceback 对象(无法直接看出有关异常的信息,还需要对其做进一步处理)

1
2
3
4
5
6
7
import sys
try:
x = int(input("请输入一个被除数:"))
print("30除以",x,"等于",30/x)
except:
print(sys.exc_info())
print("其他异常...")

要查看 traceback 对象包含的内容,需要先引进 traceback 模块,然后调用 traceback 模块中的 print_tb 方法,并将 sys.exc_info() 输出的 traceback 对象作为参数参入:

1
2
3
4
5
6
7
8
import sys
import traceback
try:
x = int(input("请输入一个被除数:"))
print("30除以",x,"等于",30/x)
except:
traceback.print_tb(sys.exc_info()[2])
print("其他异常...")
Python自定义异常类及用法

Python 允许用户自定义异常类型。

1
2
3
4
5
6
class Error(Exception):
pass
try:
raise Error()
except :
print("error")

另外,系统自带的异常只要触发会自动抛出(比如 NameError、ValueError 等),但用户自定义的异常需要用户自己决定什么时候抛出。也就是说,自定义的异常需要使用 raise 手动抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
class InputError(Exception):
'''当输出有误时,抛出此异常'''
#自定义异常类型的初始化
def __init__(self, value):
self.value = value
#返回异常类对象的说明信息
def __str__(self):
return ("{} is invalid input".format(repr(self.value)))

try:
raise InputError(1) # 抛出 MyInputError 这个异常
except InputError as err:
print('error: {}'.format(err))