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 |
传入无效的参数 |