Python异常处理

2015/04/09 Python

Python异常处理

Python具备完整而强大的异常处理能力。一个设计良好的程序应该能够事先考虑所有可能发生的情况,把这些异常的情况都拦截下来,并加以适当的处理。

程序的错误类型

在程序运行的过程中,可能会碰到许多错误,如果可以清楚地知道各种错误的类型,将便于程序设计人员进行调试和纠错。另外,在错误发生时的异常处理机制可以确保程序正常地运行与结束,而不会因为错误的发生导致程序异常中断。

编写程序的过程中,有可能因为对语法不熟悉、指令的误用或设计逻辑有误而导致程序发生错误或产生了不是原先预期的结果。因此,一个设计良好的程序,从小至一些简易计算任务的程序到大型应用程序、数据库程序或游戏的开发,都必须经过严谨和细心的调试工作,反复测试各种可能出现错误的检查点。唯有遵循这些严谨的操作流程,才可以提高程序的正确性,降低程序发生错误的概率。

通常程序的错误类型可以分为三种:语法错误、运行时错误、逻辑错误。

语法错误

语法错误是最常见的错误,这种错误有可能是编写程序时不小心写错语法所致。例如,定义函数或使用流程结构指令时忘记加“:”来形成程序区块(在Python语言中也叫suite)。

以下面的范例程序来说,就会显示“SyntaxError:invalid syntax”语法错误提示信息。

month=int(input('请输入月份: '))
if 2<=month and month<=4
    print('充满生机的春天')
elif 5<=month and month<=7:
    print('热力四射的夏季')
elif month>=8 and month <=10:
    print('落叶缤纷的秋季')
elif month==1 or (month>=11 and month<=12):
    print('寒风刺骨的冬季')
else:
    print('很抱歉没有这个月份!!!')

我们可以发现在上述程序代码中的第2行少了冒号,只要进行语法修改,就可以正常执行程序代码。

if 2<=month and month<=4:

运行时错误

运行时错误是指程序在运行期间遇到的错误,这类错误可能是逻辑上的错误,也可能是系统资源不足所造成的错误。

例如,下面的代码段打算要读取列表的数据,但却发生“IndexError:list index out of range”超出下标范围的错误:

data=[[1,2],[2,1],[1,5],[5,1], \
      [2,3],[3,2],[2,4],[4,2], \
      [3,4],[4,3]]
arr = [[0] * 6 for j in range(6)]
for i in range(14):  #读取图形数据
    for j in range(6):  #填入arr矩阵
        for k in range(6):
            tmpi=data[i][0]    #tmpi为起始顶点
            tmpj=data[i][1]    #tmpj为终止顶点
            arr[tmpi][tmpj]=1  #有边的点填入1

这个错误告诉用户的信息是:在读取列表内的元素时发生超出下标范围的错误。遇到这种错误的正确解决方法是详细检查所声明的列表元素的个数,再对比在循环中存取列表的元素时是否超出下标值所设置的范围。

data=[[1,2],[2,1],[1,5],[5,1], \
      [2,3],[3,2],[2,4],[4,2], \
      [3,4],[4,3]]
arr = [[0] * 6 for j in range(6)]
for i in range(10):  #读取图形数据
    for j in range(2):  #填入arr矩阵
        for k in range(6):
            tmpi=data[i][0]    #tmpi为起始顶点
            tmpj=data[i][1]    #tmpj为终止顶点
            arr[tmpi][tmpj]=1  #有边的点填入1

逻辑错误

逻辑错误是三种错误中最不容易被发现的错误,逻辑错误常会产生出乎我们意料的输出结果。与语法错误不同的是,逻辑错误从程序语法上来看是一段正确的程序代码,但其执行结果却与预期不符。

例如,下面的程序代码原先预期两个数的平均值是7,但执行结果却是10。这是因为第2行中要计算两个数的平均值,所以必须先用括号来优先计算两个数的总和,得到两个数的总和之后再除以2,这样才可以得到最后正确的结果。

下面的程序代码没有考虑到运算符的执行优先级,因而导致了所得到的运算结果不是原先预期的。

def average(a,b):
        return a+b/2 #此表达式的正确写法应该为 (a+b)/2
print("两个数的平均值: ",average(6,8))

上面所示范的程序是简短的程序,通过细心的调试可以比较容易地找到逻辑错误的原因。如果程序代码比较长,逻辑错误的真正原因是很不容易被发现和定位的,在程序调试过程中,如果运气比较差,就可能花费程序设计人员很长的时间才能找到逻辑错误。

认识异常

什么是异常(Exception)情况?当程序运行时,产生了不是程序设计人员原先预期的结果,也就是在程序运行过程中发生了“不可预期”的特殊情况。在这种情况下,Python解释器会“接手”进行管理,并终止程序的运行。也就是说,当发生异常情况时,如果所编写的程序没有进行任何处置,程序就会发出错误信息,同时中止程序的运行。

假如要求用户连续输入两个数字进行相除运算,第二个数字不可以为0,如果程序代码中没有编写异常处理的程序代码,用户不小心将第二个数字输入为0,就会发生除零的错误,并造成程序的中断,这当然不是一种好的处理方式。正确的做法是,一旦用户不小心在第二个数字输入了0,程序就会捕获到这个错误,然后要求用户重新输入一个非零的数字,如此一来,程序就不会因为除零这个异常情况而中止。

在示范如何使用Python提供的“异常处理机制”(Exception Handling)来捕获程序的错误之前,我们先来看看异常的类型。

异常的类型

系统会根据不同的错误情况抛出不同的异常。有关各种异常类型的详细说明,我们可以参考Python的在线帮助文件。下面只针对几种常见的异常类型进行说明。

  • LookupError:当映像或序列类型的键或下标无效时引发,所以它有两个派生类:IndexError和KeyError。其中IndexError是指下标运算符的错误。
  • NameError:是指名称没有定义的错误,例如下面的程序代码在调用函数时,函数的名称并未定义,于是引发NameError:name’inpu’is not defined的错误,其中的关键原因是漏了一个字母t,因此将函数名称修改成input即可修正这个错误。
print('1.80以上,2.60~79,3.59以下')
ch=inpu('请输入一组分数: ')
#条件语句开始
if ch=='1':
    print('继续保持!')
elif ch=='2':
    print('还有进步空间!!')
elif ch=='3':
    print('请多加努力!!!')
else:
    print('error')

或者这种情况,假如total变量是在for循环内使用的局部变量,却在for循环体之外输出total累加的结果,也会引发异常。

  • OSError:由操作系统函数发生错误时引发。
  • RuntimeError:运行时错误。
  • SyntaxError:从字面上来解释就是“语法错误”。Python解释器无法理解要解释的程序代码语法,就会引发此异常。
  • ValueError:调用内建函数时,参数中的类型正确,可是值却不正确,就会引发此异常。例如,调用input()函数来接收数据,并用int()函数转为数值,但是所输入的参数却是字符串,就会引发这类异常。
  • ZeroDivisionError:除零错误,当两个数相除时,如果除数不小心设置为0,就会引发除零的错误。
  • FileNotFoundError:这是指程序中要打开或写入的文件不存在,有可能是因为存储文件的目录位置改变了或者尚未创建该文件,还有可能是用户输入的文件名错误,造成程序找不到该指定名称的文件,于是抛出这种找不到文件的异常错误。
  • FileExistsError:这种错误通常发生在程序设计人员想在已存在的文件上重复创建新文件。
  • TypeError:无论是表达式中还是函数中的参数,当所输入的或指定的数据类型与该表达式或函数指定的类型不相符时,就会引发这类错误。
  • OverflowError:进行算术运算所得结果的数值超出该对象类型所能表示的范围,也就是通常我们所称的“溢出”错误。
  • MemoryError:内存错误。表示程序将内存耗尽了,这意味着你的程序创建了太多对象而造成内存不足的错误。

异常处理的时机

对这些常见异常类型有了基本的认识后,我们可能会想:在哪一种情况下必须加入异常处理程序呢?其实当程序与外部人员或设备进行输入输出(I/O)的互操作时,就是常见的异常处理时机。

例如,当要求用户输入数字、输入字符串或与外部数据库连接时,即使所编写的程序代码完全正确,但也有可能因为网络线路不稳定或网络中断,或者数据库连接过程发生异常,而造成程序发生错误,在这类情况下都会引发系统抛出异常,并强迫中断程序的执行。为了避免这种错误情况的发生,程序设计人员可以适当加入异常处理的程序代码,向用户输出解决步骤的提示信息或错误提示信息,并要求用户修正错误后再度尝试。在程序加入完善的异常处理机制之后,就不会因为异常的发生而提前强制中断程序的执行。

异常处理方式

清楚了解了各种常见的异常类型及异常处理的时机后,接下来我们将说明如何在Python中捕获异常

异常处理的语法

在Python中处理异常可以使用try/except指令,下面先来了解它的完整语法。

try:
    可能发生异常的程序语句
except 异常类型名:  #只处理所列出的异常情况
    处理情况一
except (异常类型名1, 异常类型名2, ...):
    处理情况二
except 异常类型名 as 名称:
    处理情况三
except :   #处理所有异常情况
    处理情况四
else :
    #未发生异常情况时相关的处理
finally :
    #无论如何,最后一定要执行finally部分的程序语句
  • try指令之后要有冒号“:”来形成程序区块(suite),并在此程序区块中加入可能引发异常的程序语句。
  • except指令配合“异常类型”用来截取或捕获try程序区块内引发的异常,而后进行相关的处理。同样的,except指令之后要用冒号“:”形成程序区块。
  • else指令则是未发生异常时所对应的程序区块。else指令是可选的,可以加入,也可以省略。
  • 无论有无异常发生,finally指令所形成的程序区块一定会被执行。finally指令为选择性指令,可以加入,也可以省略。

下面的范例程序要求用户输入总分score和总人数total,然后计算所有人员的平均分,接着令变量ave=score/total,再打印输出所有人员的分数平均值。如果输入的数值类型符合程序的需求,就不会引发异常错误,并可以正确求得所有分数的平均值。

score=eval(input("请输入数字总和: "))
total=eval(input("请输入要求平均值的数字个数: "))
ave=score/total
print("所有数字的平均值=", ave)

但是,针对上述程序代码,如果在输入过程中不小心输入了不正确类型的数据,就会产生各种情况的错误。

为了避免类似上述三种情况的错误输入行为而造成程序运行中止,我们可以使用异常处理机制将上面的程序代码改写成下面的范例程序。这个范例程序会捕获输入数字为0的错误和其他的异常,并在程序中输出如何处理各种异常情况,当在程序中加入了完善的异常处理机制后,就可以让程序正确执行,而不会出现上一个例子中的各种错误提示信息。

try:
    score=eval(input("请输入数字总和:"))
    total=eval(input("请输入要求平均值的数字个数:"))
    ave=score/total
# 程序中使用了except指令来捕获ZeroDivisionError异常情况,如果程序捕获到这种除零错误的异常情况,就会打印输出“所输入的数字总数不可以为0。”    
except ZeroDivisionError:
    print("所输入的数字总数不可以为0。")
except Exception as e1:
    print("错误信息",e1.args)
else:
    print("没有捕获到异常, 所有数字的平均值= ", ave)
finally:
    print("成功离开此异常处理的程序区块。")

用raise抛出指定的异常

除了由Python系统抛出异常情况外,在程序中还可以使用raise指令自行抛出指定的异常情况。

例如下面的程序语句会抛出一个除零的异常情况:

>>> raise Exception(12/0)
Traceback (most recent call last):
    File "<pyshell#0>", line 1, in <module>
        raise Exception(12/0)
ZeroDivisionError: division by zero

在程序捕获到异常情况后会进行处理,而程序设计人员不希望中断程序的运行,这个时候就可以使用try/except来捕获raise语句抛出的异常情况。

下面用简单的范例程序来说明。

def show(data, index):
    try:
        data[index]
    except IndexError as err:
        print(err)
        raise IndexError('下标超出边界')
    else:
        print(data[index])
number = [15, 20, 60, 100] #List
show(number, 0)
show(number, 1)
show(number, 2)
show(number, 3)
show(number, 4)

如果要触发输入错误(ValueError),可以如下编写:

try:
    n = int(input('请输入0~10的整数:'))
    if n not in range(0,11):
        raise ValueError  #自己触发异常情况
    else:
        print(n)
except ValueError:
    print('必须是0~10的整数.')

如果用户输入的不是数字,在int()转换时就会引发错误,这是解释器触发的错误。而如果用户输入的数字不是介于0~10之间,我们可以自己使用raise指令来触发异常情况。

Python的内置类Exception预定义了18类、50余种错误和警告类型,其中语法错误(SyntaxError)、模块导入错误(ImportError)、类型错误(TypeError)、索引错误(IndexError)、键错误(KeyError)、缩进错误(IndentationError)等都是最常见的错误类型。

若程序在运行过程中发生了Exception预定义的错误,程序的执行过程就会发生改变,抛出Exception异常对象,进入异常处理。如果Exception异常对象没有被捕获,程序就会执行回溯(Traceback)来终止程序。除了程序运行抛出异常,还可以使用raise关键字来触发异常。

>>> def try_error(n=0):
        try:
            if n == 0:
                raise Warning('这是警告信息')
            if n == 1:
                raise SyntaxError('这是语法错误')
            if n == 2:
                raise IndentationError('这是缩进错误')
            if n == 3:
                raise FileNotFoundError('请求不存在的文件或目录')
        except Warning as e:
            print(e)
        except (SyntaxError, IndentationError) as e:
            print(e)
        except FileNotFoundError as e:
            print(e)
        finally:
            print('善后工作')

>>> try_error()
这是警告信息
善后工作
>>> try_error(1)
这是语法错误
善后工作
>>> try_error(2)
这是缩进错误
善后工作
>>> try_error(3)
请求不存在的文件或目录
善后工作

上面的例子使用raise关键字模拟代码中可能出现的常见错误和警告,捕获并分类处理不同的错误和警告异常,最后不管是否捕获到异常都会执行finally语句。实际应用时,一个异常捕获与处理的代码结构中,finally语句是可选的,except语句至少有一个。except语句也可以不指定任何异常类型,或指定Excpetion类。所有的常规异常和警告都是由Excpetion类派生的。

Search

    微信好友

    博士的沙漏

    Table of Contents