一、异常处理的意义
1. 异常机制已经成为衡量一门编程语言是否成熟的标准之一,使用异常处理机制的Python程序会有更好的容错性。
2. 没有人能够保证自己写的程序永远不会出错,既是程序没有错误,也不能保证用户按你的意图来输入,另外还有系统的稳定性,计算硬件是否损坏,网络掉线等诸多情况。
二、异常处理机制
1. 使用try...except捕获异常
1)例如,通常在程序运行时,用户可以随意输入,程序不会因为用户的输入不合法而突然间退出,而是向用户提示输入不合法,并请用户再次输入。
那么我们就希望有一种强大的if块来解决这个非法输入的问题:
if 用户输入不合法:
alert 输入不合法
go retry
else:
#业务实现代码
2)但是“用户输入不合法”这个条件怎么定义呢?我们可以使用正则表达式与用户的输入进行匹配。但现实中不合法的情况非常多,想让程序一次处理所有的错误,我们可以将上面的伪代码修改为:
if 一切正常:
#业务实现代码
就是:
alert 输入不合法
goto retry
3) 但“一切正常”依然是很抽象的,无法转化成代码。在这种情形下Python提供了一种假设:如果程序可以顺利运行,那么就是“一切正常”。由此得出Python异常处现机制的语法结构:
try:
#业务实现代码
except(Error1, Error2, ....) as e:
alert 输入不合法
goto retry
4) 这样如果执行try块里的业务实现代码是出现异常,系统会自动生成一个异常对象,该异常对象被提交给Python解释器,这个过程被称为引发异常。
5)当Python解释器收到异常对象时,会寻找处理该异常对象的except块,如果找到合适的except块,则把该过程称为捕捉异常。如果Python解释器找到不捕获异常的except块,则运行时环境终止,Python解释器也将退出。
6)不管代码块是否处于try中,或except块中,只要执行该代码时了现了异常,系统总会自动生成一个Error对象。
7) try 可以有多个except块,这是为了针对不同的异常类提供不同的异常处理方式。当系统发生不同意外情况时,系统会生成不同的异常对象。如果try被执行一次,,则try后面只有一个except被执行,除非放在循环中,使用continue开始下一次循环。
2. 异常类
1)Python的所有异常类的基类是BaseException, 但是如果用户果自定义异常,则一概继承Exception类。
2)BaseException的主要子类是Exception,所在不管是系统的异常类,还是用户自定义异常类,都是从Exception派生。
3)异常捕获实例:
>>> import sys
>>> try:
a = int(sys.argv[1]) # 代表当前运行的程序所提供的第一个参数
b = int(sys.argv[2])# 代表当前运行的程序所提供的第二个参数
c = a/b
print("您输入的两个数相除的结果是:",c)
except IndexError:
print("索引错误:运行程序时输入的参数个数不够")
except ValueError:
print("数值错误:程序只能接受整数参数")
except ArithmeticError:
print("算术错误")
except Exception:
print("未知异常")
3. 多异常捕获:指一个Except块可以捕获多种类型的异常。例如:
>>> import sys
>>> try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a/b
print("您输入的两个数相除的结果是:",c)
except (IndexError, ValueError, ArithmeticError):
print("程序发生了数组越界、格式错误、算术异常之一")
except: # 此处的省略也是合法的,一般放在最后
print("未知异常")
程序发生了数组越界、格式错误、算术异常之一
4. 访问异常信息
1)如果程序需要在except块中访问异常对象的相关信息,则可以通过为异常对象声明变量来实现。
2)所在的异常对象都包含如下几个对象和方法:
args: 该属性返回异常的错误编号和描述字符串
errno:该属性返回异常的错误编号
strerror:该属性返回异常的描述字符串
with_traceback():通过该方法可以处理异常的传播轨迹
3)程序访问异常信息实例:
>>> def foo():
try:
fis = open("a.txt")
except Exception as e:
print(e.args)
print(e.errno)
print(e.strerror)
>>> foo()
#运行结果如下:
(2, 'No such file or directory')
2
No such file or directory
5. else块
1) 在Python异常处理流程中还可添加一个else块,当try块没有出现异常时,程序会执行else块。
例如:
>>> s = input("请输入除数:")
请输入除数:5
>>> try:
result = 20/int(s)
print('20除以%s的结果是:%g'%(s,result))
except ValueError:
print('值错误,您必须输入数值')
except ArithmeticError:
print('算术错误,您不能输入0')
else:
print('没有出现异常')
#运行结果如下:
20除以5的结果是:4
没有出现异常
2)实际上大部分语言异常处理都没有else块,可以将else块的内容直接放到try后面。但Python异常处理使用else块也不是多余的语法。因为在else块中的异常不会被except捕获,该异常会传给Python解释器导致程序中止。
3)如果希望某段代码的异常,能被后面的except捕获,那么就应该放在try块中;如果不希望被except捕获,就应该放在else块中。
6. 使用finally回收资源
1)为了能够保证回收try块中打开的一些物理资源(如数据库连接、网络连接和磁盘文件),异常处理机制提供了finally块。
2)Python 完整的异常处理语法结构如下:
try:
#业务实现代码
except SubException1 as e:
#异常处理块1
except SubException2 as e:
#异常处理块2
else:
#正常处理块
finally:
#资源回收块
三、使用raise引发异常
1. 如果程序中数据或执行与现实的需求不符,但是系统不会判断这样的异常,只能程序员来决定是否引发异常,此时可以使用raise语句来完成这种自行引发的异常。
2. raise语句三种常见的用法:
1) 单独一个raise,该语句引发当前上下文中捕获的异常,或默认引发RuntimeError异常。
2)raise后带一个异常类,该语句引发指定的异常类。
3)raise后带一个异常对象,该语句引发制定的异常对象。
以上三种最终都是引发一个异常实例,每次只能引发一个异常实例。
3. 用户引发异常的两种方式:raise和except,例如:
>>> def main():
try: # 使用try...except来捕获异常,此时出现异常也不会传给调用它的main()函数
mtd(3)
except Exception as e:
print('程序出现异常类是:',e)
mtd(3) #不使用try...except来捕获异常,异常会传播并导致程序中止
>>> def mtd(a):
if a>0:
raise ValueError("a的值大于0,不符合要求")
>>> main()
四、异常的传播轨迹
1. 异常只要没有被完全捕获,异常就会从发生异常的函数或方法向外传播,首先传给该函数或方法的调用者,然后,,直到传给Python解释器,Python解释器就会中止程序,并打印异常传播的轨迹信息。所以通常我们看到大段的异常信息,并不一定发生很多严重的问题,可能只是一个异常引发的。
2. Python专门提供trackback模块来处理异常传播的轨迹,两种常用的方法是:
1)trackback.print_exc(): 将异常传播轨迹输出到控制台或文件中。
2) format_exc():将异常传播轨迹转换成字符串。
五、异常处理规则
1. 不要过度使用异常处理,注意以下两点:
1) 需要编写错误处理代码,而不是简单的用异常来代替处理。
2) 不能用异常来代替流程控制。
2. 不要使用过度庞大的try块:try块越大,发生异常的可能性就越大。而且在庞大的try块后势必有大量的except块,这时要判断各个块之间的逻辑关系,编写程序会变得更加复杂。
3. 不要忽略捕捉到的异常: 程序应该尽量修改异常,保证程序继续运行;不要将本层的异常传给上一层去处理。
六、本节回顾
1. 你怎么理解异常处理机制?
2. Python异常处理的5个关键字是什么?(try\ except\else\finally\raise)
3. 如何使用raise引发异常?
4. 如何获得异常的源头和轨迹?
5.异常处理的原则有哪些?