2.5 异常与警告

异常和警告是编写程序过程中经常遇到的问题。为了更好地处理编程中遇到的问题,我们需要了解异常和警告的相关知识。

2.5.1 异常

1. 捕捉异常

我们在运行代码时经常遇到程序出错的情况,在Python中,这些错误通常叫做异常(Exception)。

例如,有这样一个用于计算以10为底的对数的程序,该程序使用raw_input()函数从命令行读取输入,计算它的对数并输出结果,直到我们输入为q为止:

乍看起来,程序似乎没甚么问题,然而,当我们输入一个负数时,程序抛出了一个ValueError异常,因为对数函数不能接受一个非正值输入:

正常情况下,Python程序在抛出异常后就会停止执行。如果不希望程序停止运行,可以使用一对关键字try和except来处理异常,其基本形式如下:

我们将可能抛出异常的代码,放入try块中,然后使用except块处理相应的异常。

当try块中的代码遇到异常时,这个异常会首先被传到except块。如果except块能处理这个异常,则执行except块相应的内容,然后程序继续执行;如果不能,该异常将被继续传递。

在上面的例子中,代码抛出的异常类型是ValueError,因此,我们改写程序,将可能出错的部分放入try块,并用关键字except处理ValueError类型的异常:

再执行这个程序时,输入负数或者0都不会中断程序,而是打印expect块输出的信息:

2. 处理不同类型的错误信息

我们对上面的代码进行修改,将y的值改为1/math.log10(x):

如果输入1:

因为1的对数为0,而1/0是一个非法操作,所以Python抛出了一个ZeroDivisionError类型的异常。

这个异常首先传到except块中,但我们定义的except块并不能处理这种类型的异常,因此,该异常被继续传递,程序停止运行。

这个问题有以下几种解决方式。

1)第一种方式,可以使用Exception替换ValueError,直接捕获所有的异常。Exception类型是各种异常的总称,所以ZeroDivisionError类型和ValueError类型都是一种特殊的Exception:

因此,下面的异常都会被except块处理:

2)第二种方式,我们可以在一个except块中声明多个异常类型:

程序运行抛出其中任意一种类型的异常都会被except块所处理。

3)第三种方式,通过多个except块分别处理各种类型的异常,每个except块负责处理一种类型的异常:

在这种情况下,两种类型的异常会被程序分别处理:

3. 得到异常的具体信息

当我们输入字符串“abcde”时,上面的程序会提示:“the value must be greater than 0”,这与实际情况不符。抛出ValueError异常的部分并不是math.log10()函数,而是float()函数。

调用float('abcde')时,由于所给字符串不能转化为浮点数,Python会抛出一个“ValueError: could not convert string to float: abcde”的异常。这个异常包含两部分的内容:前面的部分表示异常的类型,后面的部分表示异常的具体说明。

在except块中,我们可以这样获得异常的具体信息:

利用这种方式,我们首先将捕获到的异常保存在变量e中,并用属性.message查看相关的说明信息。

为了得到异常的具体信息,修改except块的部分:

运行后,当我们输入非法值时,就能得到异常的具体信息:

4. 抛出异常

在程序运行过程中,我们可以使用关键字raise抛出异常。

例如,当变量month为不合法的月份时抛出异常:

抛出的异常类型为ValueError,括号中为具体说明信息。

5. finally关键字

异常处理时,我们还可以加入一个以关键字finally开头的代码块,其作用为:不管try块中的代码是否抛出异常,finally块中的内容总是会被执行。

没有异常时,finally块会在try块的代码执行完毕后执行;出现异常时,finally块会在抛出异常前执行。因此,finally块可以用来作为程序抛出异常时的安全保证,比如确保打开的文件被正确关闭。

例如,一个没有异常的finally块:

如果异常被except块处理了,finally块在异常被处理后执行:

finally块的执行顺序总结如下:

● 没有异常,try块结束后执行;

● 异常抛出,except块没有处理异常,在抛出异常前执行;

● 异常抛出,except块处理了异常,在异常被处理后执行。

2.5.2 警告

在Python中,警告(Warning)通常用来告知用户某种做法是不好的,但这种做法不会影响程序的正常运行。

使用警告需要预先导入相关的模块:

In [1]: import warnings

然后调用warnings模块中的warn函数来抛出警告:

warn(msg, WarningType = UserWarning)

msg是警告的提示信息,WarningType参数用来指定警告的类型,如果不指定,默认的类型是UserWarning(用户警告):

In [2]: warnings.warn("test")

C:\Miniconda2\Scripts\ipython-script.py:1: UserWarning: test

常见的警告类型主要有:

● Warning,所有警告的父类,所有的警告都能看成一个Warning类;

● UserWarning,用户警告,warn函数的默认类型;

● DeprecationWarning,表示用户使用了未来会被废弃的功能;

● FutureWarning,表示用户使用了未来可能会改变的功能;

● RuntimeWarning,运行时警告。

有时候,我们在运行程序时不希望看见某种类型的警告,可以使用warnings模块中的filterwarnings来进行筛选:

在程序运行时,所有RuntimeWarning类型的警告都不会被显示。