Python的异常机制主要依赖try、except、else、finally和raise五个关键字,其中try块中放置的是可能引发异常的代码;except后对应处理这种异常的代码;在多个except块之后可以放一个else,表明程序不出现异常时还要执行else;最后还可以跟一个finally,用于回收在try块里打开的物理资源,异常机制会保证finally块总是被执行;而raise用于引发一个实际的异常,raise可以单独作为语句使用。
错误处理机制,主要有如下两个缺点:
● 无法穷举所有的异常情况。因为人类知识的限制,异常情况总比可以考虑到的情况多,总有“漏网之鱼”的异常情况,所以程序总是不够健壮。
● 错误处理代码和业务实现代码混杂。这种错误处理和业务实现混杂的代码严重影响程序的可读性,会增加程序的维护难度。
当程序运行出现意外情况时,系统会自动生成一个Error对象来通知程序,从而实现将“业务实现代码”和“错误处理代码”分离.
异常处理机制的语法结构:
try:
# 业务实现代码
...
except (Error1, Error2, ...) as e:
alert 输入不合法
goto retry
如果在执行try块里的业务逻辑代码出现异常,系统自动生成一个异常对象,该异常对象被提交给Python解释器,这个过程被称为引发异常。
当Python解释器收到异常对象时,会寻找能处理该异常对象的except块,如果找到合适的except块,则把该异常对象交给该except块处理,这个过程被称为异常捕获。
如果Python解释器找不到异常捕获的except块,则运行环境终止,Python解释器也将退出。
Python的所有异常类的基类是BaseException,但如果用户要实现自定义异常,则不应该继承这个基类,而是继承Exception类。不管是系统异常类,还是用户自定义异常类都应该从Exception派生。
虽然Python语法没有要求,但在实际编程时一定要记住先捕获小异常,再捕获大异常。
在使用一个except块捕获多种类型的异常时,只要将多个异常类用圆括号括起来,中间用逗号隔开即可——其实就是构建多个异常类的元组。
所有的异常对象都包含了一下几个常用的属性和方法:
● args:该属性返回异常的错误编号和描述字符串。
● errno:该属性返回异常的错误编号
● strerror:该属性返回异常的描述字符串
● with_traceback():通过该方法可处理异常的传播轨迹信息。
在Python的异常处理流程中还可以添加一个else块,当try块没有出现异常时,程序会执行else块。
如果希望某段代码的异常能被后面的except块捕获,那么就应该将这段代码放在try块的代码之后;如果希望某段代码的异常能向外传播(不被except块捕获),就应该将这段代码放在else块中。
不管try块中的代码是否出现异常,也不管哪一个except块被执行,甚至try块或except块中执行了return语句,finally块总会被执行。
在异常处理语法结构中,只有try块是必须的。
不要在finally块中使用如return或raise等导致方法中止的语句,一点在finally块中使用了return或raise语句,将会导致try块、except块中的return、raise语句失效。
异常可以被嵌套,在try块、except块或finally块中包含完整的异常处理流程的情形被称为异常处理嵌套。
对异常处理嵌套的深度没有明确的限制,但通常没有必要使用超过两层的嵌套异常处理。
如果需要在程序中自行引发异常,则应使用raise语句。raise语句有如下三种常用的用法:
● raise:单独一个raise,该语句引发当前上下文中捕获的异常(比如在except块中),或默认引发RuntimeError异常。
● raise 异常类:raise后带一个异常类。该语句引发指定异常类的默认实例。
● raise 异常对象:引发指定的异常对象。
不管是系统自动引发的异常,还是程序手动引发的异常,Python解释器对异常的处理没有任何差别。
异常对象提供了一个with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹可追踪异常触发的源头,也可看到异常一路触发的轨迹。
只要异常没有被完全捕获(包括异常没有被捕获,或者异常被处理后重新引发了新异常),异常就从发生异常的函数或方法逐渐向外传播,首先传给该函数或方法的调用者,再传给其他调用者,直至最后传到Python解释器,此时Python解释器会终止程序,并打印异常的传播轨迹信息。
Python专门提供了traceback模块来处理异常传播轨迹。
● traceback.print_exec():将异常传播轨迹信息输出到控制台或指定文件中。
● format_exec():将异常传播轨迹信息转换成字符串。
成功的异常处理应该实现如下4个目标:
● 是程序代码混乱最小化
● 捕获并保留诊断信息
● 通知合适的人员
● 采用合适的方式结束异常活动
异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。
通常建议对异常采取适当措施,如:
● 处理异常。对异常进行合适的修复,然后绕过异常发生的地方继续运行;或者用别的数据进行计算,以代替期望的方法返回值:或者提示用户重新操作
● 重新引发新异常。把在当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成当前层的异常,重新传给上层调用者。
● 在合适的层处理异常。如果当前层不清楚如何处理异常,就不要在当前层使用except语句来捕获异常,让上层调用者来负责处理该异常。
习题:
1. 提示用户输入一个N , 表示用户接下来要输入N 个宇符串,程序尝试将用户输入的每一个字符串用空格分割成两个整数,并结算这两个整数整除的结果。要求: 使用异常处理机制来处理用户输入的各种错误情况,并提示用户重新输入。
n = input('请输入一个整数:')
try:
num = int(n)
i = 0
while(True):
num_str = input('请输入以空格分隔的两个整数:')
try:
a, b = num_str.split(' ')
print("%d整除%d的结果为:%d" % (int(a), int(b), int(a) // int(b)))
i += 1
if i >= num:
break
except ValueError:
print("必须输入两个以空格隔开的数字,请重新输入!")
continue
except ValueError:
print("请输入一个数字")
2. 提示用户输入一个整数,如果用户输入的整数是奇数,则输出“有趣”;如果用户输入的整数是偶数,且在2~5 之间,则打印“没意思”;如果用户输入的整数是偶数,且在6~20 之间,则输出“有趣”;如果输入的整数是其他偶数,则打印“没意思”。要求:使用异常处理机制来处理用户输入的各种错误情况。
n = input('请输入一个整数:')
try:
n = int(n)
if (n % 2 == 0) and (n >= 2) and (n <= 5):
print('没意思!')
elif (n % 2 == 0) and (n >= 6) and (n <= 20):
print('有趣!')
elif (n % 2 != 0):
print('有趣!')
except ValueError:
print('输入的必须是一个数字!')
3 . 提供一个字符串元组,程序要求元组中每一个元素的长度都在5~20 之间:否则, 程序引发异常。
temp_str = ('this is test', 'test the str', 'test')
for s in temp_str:
if (len(s) < 5) or (len(s) > 20):
raise ValueError('元组的每个元素长度必须在5~20之间')
4. 提示用户输入x1, y1, x2, y2, x3, y3 六个数值,分别代表三个点的坐标,程序判断这三个点是否在同一条直线上。要求: 使用异常处理机制处理用户输入的各种错误情况,如果三个点不在同一条直线上,则程序出现异常。
try:
x1, y1, x2, y2, x3, y3 = input('请依次输入6个坐标点以空格隔开').split(' ')
x1 = float(x1)
y1 = float(y1)
x2 = float(x2)
y2 = float(y2)
x3 = float(x3)
y3 = float(y3)
if x1 == 0 and x2 == 0 and x3 == 0:
print('三个点在一条直线上')
elif 0 in (x1, x2, x3):
raise Exception('三个点不在同一直线上')
elif y1 / x1 == y2 / x2 and y1 / x1 == y3 / x3:
print('三个点在同一直线上')
else:
raise Exception('三个点不在同一直线上')
except ValueError:
print('输入的坐标必须为数字!')