10.1  异常概念与常见表现形式

异常是一个事件,这个事件会在程序执行过程中发生,影响程序的正常进行。一般情况下,在Python无法正常进行程序时就会发生异常。异常是Python的对象,它表示一个错误,在Python脚本在发生异常时,需要对异常进行捕获及处理,否则程序会被终止执行。

每个异常都是一些类的实例,这些实例可以被引用,并且可以用很多种方法进行捕捉,使得这些错误可以被处理,而不是让整个进程失败。

需要注意的是,异常和错误的概念并不相同。异常一般是指运行时由于某些条件不符合而引发的错误,一但引发异常并且没有得到有效的处理,一般是直接导致程序崩溃。错误又可以分为语法错误和逻辑错误两种。拼写错误、缩进不一致、引号或括号不闭合等问题都属于语法错误,一般来说,存在语法错误的代码是无法运行的,这类错误很容易发现和解决;而存在逻辑错误代码则通常可以运行,但是非常可能会得到一个错误的结果,这类错误非常难发现。

常见的异常表现形式有以下几种,如下表10.1。

表10.1  Python常见标准异常表

表现形式

功能

SystemExit

解释器请求退出

Warning

警告的基类

RuntimeError

一般的运行时错误

KeyError

映射中没有这个键

SystemError

一般的解释器系统错误

AttributeError

对象没有这个属性

IndexError

序列中没有此索引

MemoryError

内存溢出错误(对于Python解释器不是致命的)

WindowsError

系统调用失败

IOError

输入/输出操作失败

OverflowError

树枝运算超出最大限制

UserWarning

用户代码生成的警告

 

10.2  常见异常处理结构

当编程人员在执行代码时,有些代码可能会出现错误,也有可能不会出现错误,这主要由运行时的各种客观因素来决定,此时建议使用异常处理结构。如果使用大量的选择结构来提前进行判断,仅当满足相应的条件时才执行该代码,这些条件判断可能会严重干扰正常的业务逻辑,也会严重降低代码的可读性,所以需要使用异常处理结构来保证业务逻辑的正常运行。Python提供了多种不同形式的异常处理结构,它们的基本思路都是一致的;先尝试运行代码,然后处理可能发生的错误。在实际使用时,可以根据需要来选择使用哪一种。

10.2.1  try...except...

在Python异常处理结构中最基本的结构是结构。其中try子句中的代码块包含着可能会引发异常的语句,而except子句则用来捕捉相应的异常。如果try子句中的代码引发异常并被except子句捕捉,则执行except子句的代码块;如果try子句中的代码块没有出现异常,则继续往下执行异常处理结构之后的代码;如果出现异常但没有被except捕获,则继续往外层抛出;如果所有层都没有捕获并处理该异常,则程序崩溃并将该异常呈现给最终用户,而这是编程者最不希望发生的事情。该结构语法如下:

try

#可能会引发异常的代码,先执行一下试试

except Exception[ as reason]:

#如果try中的代码抛出异常并被except捕捉,就执行这里的代码

 

该结构的示例代码如下:

#!/usr/bin/env python

# -*- coding: UTF-8 -*-

def exp_exception(x,y):

try:

a = x/y

print('a=',a)

return a

except Exception:

print('程序出现异常,异常信息:被除数为0')

 

exp_exception(2,0)

 

上方代码执行后输出结果如下:

程序出现异常,异常信息:被除数为0

 

由执行结果可见,程序最后执行的是except子句,如果语句未出现异常,则应该输出”a=”的形式。

在此段代码中,也可以直接在做除法前对y值进行判断来解决问题,也可以解决问题,但是如果给程序程序加入更多除法,就得给每个除法语句上都加一个判断语句,这样整个代码看上去就是一堆类似if的功能重复判断语句,真正有效的代码没有多少。而使用try/except语句结构只需要一个错误处理器即可,省时省力。

 

10.2.2  try...except...else...

带有else子句的异常处理结构可以看做是一种特殊的选择结构,如果try子句中的代码抛出了异常并且被某个except语句捕捉则执行相应的异常处理代码,这种情况下就不会执行else中的代码;如果try子句中的代码没有抛出异常,则执行else子句中的代码块。该结构的语法如下:

try

#可能会引发异常的代码块

except Exception [ as reason]

#用来处理异常的代码

else:

#如果try子句中的代码没有引发异常,就继续执行这里的代码

 

用该结构来要求用户必须输入整数,该结构的示例代码如下:

>>> while True

x = input('Please input:')

try:

x = int(x)

except Exception as e:

print('Error.')

else:

print('You have input {0}'.format(x))

break

Please input:a

Error.

Please input:b

Error.

Please input:888c

Error.

Please input:888

You have input 888

 

10.2.3  带多个except的异常处理结构

在实际开发中,同一段代码可能会抛出多种异常,并且需要针对不同的异常类型对代码进行相应的处理。为了支持多种异常的捕获和处理,Python提供了带有多个except的异常处理结构,一但某个except子句捕捉到了异常,则其它的except子句将不会再尝试捕捉异常。该结构类似于多分支选择结构,它的语法格式为:

>>> try:

x = float(input('请输入被除数:'))

y = float(input('请输入除数:'))

z = float(x)/y

except ZeroDivisionError:

print('除数不能为零')

except TypeError:

print('被除数和除数应为数值类型')

except NameError:

print('变量不存在')

else:

print(x,'/',y,'=',z)

 

请输入被除数:30 #第一次运行

请输入除数:5

30.0 / 5.0=6.0

 

请输入被除数:30 #第二次运行,略去重复代码

请输入除数:abc

Traceback(most recent call last):

File”<pyshell#95>”,line 3,in<moudle>

y = float(input('请输入除数:'))

ValueError:could not convert string to float:'abc'

 

请输入被除数:30 #第三次运行,略去重复代码

请输入除数:0

除数不能为零

 

在实际开发中,有时候可能会为几种不同的异常设计相同的处理代码。为了减少代码量,Python允许将多个异常类型放到同一个元组中,然后使用一个except子句同时进行捕捉多种异常,并且共用同一段异常处理代码,例如:

>>> try:

x = float(input('请输入被除数:'))

y = float(input('请输入除数:'))

z = float(x)/y

except(ZeroDivisionError,TypeError,NameError):

print('捕捉到了异常')

else:

print(x,'/',y,'=',z)

 

请输入被除数:30

请输入除数:0

捕捉到了异常

 

10.2.4  try...finally...

在此结构中,无论try中的代码是否会发生异常,也不管抛出的异常有没有被except语句捕捉到,finally子句中的代码总会得到执行。因此,finally语句中的代码常用来做一些清理工作以释放try子句中申请的资源。

如果try子句中的异常没有被except捕获和处理,或者except子句或else子句中的代码抛出了异常,那么这些异常将会在finally子句执行完毕后再次抛出。

如果在函数中使用异常处理结构,尽量不要在finally子句中使用return语句,以免发生非常难以发现的逻辑错误。该语法结构如下:

try:

#可能会引发异常的代码

except Exception [ as reason]:

#处理异常的代码

finally:

#无论try子句中的代码是否引发异常,都会执行此子句中的代码块

 

finally子句中的代码也有可能会引发异常。例如下面的代码,本意是使用异常处理结构来避免文件对象没有关闭的情况发生,但是由于指定的文件不存在,导致打开文件失败,结果在finally子句中关闭文件时引发了异常。示例代码如下:

>>> try:

f1 = open('test1.txt','r') #文件不存在,抛出异常,不会创建文件对象f1

line = f1.readline() #后面的代码不会被执行

print(line)

except SyntaxError: #这个except并不能捕捉上面的异常

print('Sth wrong')

finally:

f1.close() #f1不存在,再次引发异常

 

Traceback(most recent call last):

File”<pyshell#75>”,line 2,in<moudle>

f1 = open('test1.txt','r')

FileNotFoundError:[Error2]No such file or directory:'test1.txt'

 

During handling of the above exception,another exception occurred:

Traceback(most recent call last):

File”<pyshell#75>”,line 8,in<moudle>

f1.close()

NameError:name 'f1'is not defined

10.3  raise语句

Python使用raise语句抛出一个指定异常。使用者可以使用类(Exception的子类)或实例参数来调用raise语句引发异常。使用类时程序会自动创建实例。

>>> raise Exception

Traceback(most recent call last):

File”<pyshell#1>”,line 1,in <module>

raise Exception

Exception

>>> raise NameError('This is NameError')

Traceback(most recent call last):

File”<pyshell#0>”,line 1,in <module>

raise NameError('This is NameError')

NameError:This is NameError

由操作结果可得,第一个示例raise Exception引发了一个没有相关错误信息的普通异常,第二个示例输出了一些错误提示。

如果只想知道是否抛出了异常,并不想处理,使用一个简单的raise语句就可以再次把异常抛出,例如:

>>> try:

raise NameError('This is NameError')

except NameError:

print('An exception happened!')

 

An exception happened!

 

>>> try:

raise NameError('This is NameError')

except NameError:

print('An exception happened!')

raise

 

An exception happened!

Traceback(most recent call last):

File”<pyshell#11>”,line 2,in <module>

raise NameError('This is NameError')

NameError:This is NameError

 

由输出结果可得,使用raise可以输出更深层次的异常。在使用过程中,可以借助该方法得到更详尽的异常信息。

前面碰到的NameError,SyntaxError、TypeError、ValueError等异常类称为内建异常类。在Python中,内建的异常类有很多,可以使用dir函数列出异常类的内容,并用在raise语句中,用法如raise NameError这般。表10.2列出了一些重要的内建异常类。

表10.2 Python重要的内建异常类

异常名称

描述

Exception

常规错误的基类

AttributeError

对象没有这个属性

IOError

输入/输出操作失败

IndexError

序列中没有此索引

KeyError

映射中没有这个键

NameError

未声明/初始化对象(没有属性)

SyntaxError

Python语法错误

SystemError

一般解释器系统错误

ValueError

传入无效的参数