Table of Contents
在编写程序的时候,有时候会遇到程序异常的情况。譬如对0进行整除,我们可以对每个运算都进行条件判断,然而这种做法效率低且灵活性很差,使得代码冗杂,生涩难读。这时候,可以用python提供的「异常处理机制」。
1 异常是什么
在介绍这个机制之前,先必须明了,异常是什么?
在自然语言中,「异常」是正常情况以外的情异常况,具有不确定性。python使用异常对象来表示异常状态,每个异常都是某个异常类的实例,这一点和java类似。
2如何自主地抛出异常
通常,当我们写的程序出现不可预测的问题时,都会自动引发异常。那么如何自主地抛出异常呢?
要引发异常使用的是raise语句,并将一个Exception类或者他的子类作为参数。例如:
>>> raise Exception('这里写提示信息')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: 这里写提示信息
在java中所有的异常类的祖先类都是Exception,这一点python也几乎是一样的。
python内置了一些异常类,他们都可以作为raise语句的参数。
类名 | 描述 |
Exception | 几乎所有的异常类都是她的子类 |
AttributeError | 引用属性或者给他赋值的时候失败 |
OSError | 操作系统无法执行指定的任务 |
IndexError | 访问序列中不存在的索引 |
KeyError | 使用映射中不存在的Key |
NameError | 找不到变量的名称 |
SyntaxError | 代码不正确 |
TypeError | 将内置操作或者函数用于类型不正确的对象 |
ValueError | 内置操作或者函数的对象类型正确但值错误 |
以上只是一些常用的内置异常。
3 如何自定义异常类
虽然内置异常的涉及范围很广,但有时我们想使用自己的异常类。如何自定义异常类?
异常类必须直接或者间接继承Exception,例如:
>>> class MyException(Exception): pass
4捕获异常
异常捕获一般使用try/exception语句,例如:
try:
x=int(input('input N'))
if (x==0):
raise Exception
y=np.log(x)
except Exception:
print('N can not be zero')
exception … :
…
异常从函数的内部向外部传播,如果在函数内不捕获,在调用函数的地方也可以捕获。但如果在函数内捕获了,外部就无法捕获了。
当我们在函数内捕获了异常,并且还想让他继续传播到调用函数的地方,可以在捕获的时候调用raise,例如:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
当给raise加上参数的时候,发生了:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise ValueError
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError
正如解释器所说的,在处理当前异常的时候,另一个异常也被捕获了。这是因为1/0引发的ZeroDivisionError异常被当作ValueError异常的上下文存储起来了。
可以使用语句 raise … from … 来自己提供异常上下文,例如:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise ValueError from TypeError
...
TypeError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError
根据提示信息可知,‘from’后的 异常是‘raise’后的异常的直接原因。
当使用‘raise … from None’时,可以消除上下文的关联。
在上面的一个例子中,曾经给出了多个except语句来捕获不同的异常,例如:
try:
…
except ZeroDivisionError:
…
except ValueError:
…
当我们想要用一个excep语句捕获多个异常的时候,可以在一个元组中指定这些异常,例如:
try:
…
except (ZeroDivisionError,… ):
…
5捕获对象
当我们想要访问异常的对象的时候,可以在异常类后面增加一个参数,例如:
>>> try:
... 'x'/0
... except (ZeroDivisionError,TypeError) as e:
... print(e)
...
unsupported operand type(s) for /: 'str' and 'int'
可以看到,上述的式子出发了两个异常,但最终捕获的只有一个,这是因为异常机制只捕获最近产生的异常,Traceback (most recent call last)。
6当没有出现异常的时候
在有些情况下,我们不会出发任何异常,这样就可以仿照条件语句,添加一个else语句:
>>> try:
... x=1/2
... except:
... print("exception occured")
... else:
... print("万事大吉")
...
万事大吉
7finally
我想在设计外卖平台的时候的时候,一个点单行为一般会先产生一个订单,然后跳转到支付界面,当用户完成支付,商家确认之后,这条订单才算真正完成。
然而,在订单产生之后的各个环节都有可能出现异常,譬如用户没有支付或者支付失败。这时候就可以用final语句,来删除这条订单。
finally子句可以用于发生异常时执行清理工作,它可以捕获所有的异常。
譬如上面的例子:
try:
一个订单X产生
支付
商家确认
finally:
删除订单
上面这个例子是我现编的,不知道真实的公司会不会这么草率地删除一项记录。
不过finally真的非常适合用于确保文件或者网络套接字等关掉。
8警告
编程的时候会遇到一些警告,警告类似于异常。但他们通常都是只打印一条错误消息。
例如:
>>> import warnings as win
>>> win.warn("this is a warning")
__main__:1: UserWarning: this is a warning
>>> win.warn("this is second warning")
__main__:1: UserWarning: this is second warning
>>> win.warn("this is a warning")
警告只显示一次,如果再次运行一次win.warn("this is a warning"),是不会显示警告内容的。
每当生成一个警告时,都需要将其与注册过的过滤器比较,第一个匹配的过滤器将控制这个警告采取的动作,否则采取默认的动作,其实本质流程上会发现与Exception异常处理类似,看一下常见的过滤器动作:
error | 将警告提升为异常 |
ignore | 忽略警告 |
always | 总是抛出警告 |
default | 从各个位置第一次生成警告时输出警告 |
module | 从各个模块第一次生成警告时输出警告 |
once | 第一次生成警告时输出警告 |
有时候的一些警告没有什么特殊的意义,我们可以使用函数将他们抑制,例如:
>>> win.filterwarnings("ignore")
>>> win.warn("this is second warning ")
>>> win.warn("this is second warning ")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UserWarning: this is second warning
上面分别展示了ignore和error两种状况,分别将警告忽略或者转成错误。
一般而言,一个警告只出现一次,但是使用always可以让他每次执行都出现,
>>> win.warn("this is third warning",)
__main__:1: UserWarning: this is third warning
>>> win.warn("this is third warning",)
__main__:1: UserWarning: this is third warning