一、 异常处理机制
- 异常处理使得程序具有极好的容错性,让程序更加健壮。
- 分离了“业务实现”和“错误处理”代码,提供了更好的程序可读性。
- 异常处理机制主要依赖try,except,else,finally和raise五个关键字。
- 异常处理可以嵌套,但通常不建议超过2层。嵌套过多可能导致程序性能下降,同时降低了可读性。
1. try...except...
#伪代码
try:
#业务实现代码
...
except Error type1:
#异常处理1
except Error type12:
#异常处理2
引发异常过程:若执行try块中业务代码出现了异常,系统自动生成一个异常对象,异常对象会被提交给python解释器。
捕获异常过程:python解释器收到异常对象时,会依次寻找能处理该异常对象的except块(最可能的放最前面)。若找到匹配的,则将该异常对象交给该except块处理,若找不到则报错。
python常见异常类继承关系及异常关系参考
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
inputStr=input("请输入除数:")
try:
result=20/int(inputStr)
print("20除以%s的结果为:%g" % (inputStr,result))
except ValueError:
print("输入非数值,请输入一个非0数字")
except ArithmeticError:
print("输入值为0,请输入一个非0数字")
#测试结果
请输入除数:dsads200
输入非数值,请输入一个非0数字
请输入除数:0
输入值为0,请输入一个非0数字
请输入除数:100
20除以100的结果为:0.2
2. 多异常捕获
可以使用一个except块捕获多种类型异常,其实就是构建多个异常类的元组,但这种报错可能过于笼统。
inputStr=input("请输入除数:")
try:
result=20/int(inputStr)
print("20除以%s的结果为:%g" % (inputStr,result))
except (ValueError,ArithmeticError):
print("输入了非数值或0,请输入一个非0数字")
#测试结果
请输入除数:kkk
输入了非数值或0,请输入一个非0数字
请输入除数:0
输入了非数值或0,请输入一个非0数字
请输入除数:30
20除以30的结果为:0.666667
3. 访问异常信息
python会将异常对象赋值给except块后的异常变量,程序可通过这些变量访问对象信息
inputStr=input("请输入除数:")
try:
result=20/int(inputStr)
print("20除以%s的结果为:%g" % (inputStr,result))
except Exception as e:
print("输入了非数值或0,请输入一个非0数字")
#异常错误号和详细信息
print(e.args)
#输出结果
请输入除数:0
输入了非数值或0,请输入一个非0数字
('division by zero',)
4. else块
异常处理流程中还可添加else块,当try块中没有出现异常时,程序会执行else块中内容。(可以但没必要,没有异常通常直接放在try后执行就可以了)
inputStr=input("请输入除数:")
try:
result=20/int(inputStr)
print("20除以%s的结果为:%g" % (inputStr,result))
except Exception as e:
print("输入了非数值或0,请输入一个非0数字")
else:
print("未发生异常")
#输出结果
请输入除数:100
20除以100的结果为:0.2
未发生异常
5. 使用finally回收资源
- 除非在程序中使用os._exit(1)强制退出python编译器,否则无论是否发生异常,也无论异常在哪个except块被捕获(包括在try或except中有return语句),finally块总会被执行。
- finally块必须位于所有的except块后
- 通常情况下,不要在finally块中使用return,raise等导致方法中止的语句,它会让try和except块中的return,raise语句失效(因为无论前面返回什么都会被finally块的return覆盖)
import os
def test():
fis=None
try:
fis=open("a.txt")
except OSError as e:
#返回详细报错信息
print(e.strerror)
#测试return返回,仍会执行finally部分
return
#删除下面注释,os._exit(1)会直接退出python编译器,不执行finally部分
#os._exit(1)
finally:
#关闭磁盘文件,回收资源
if fis is not None:
try:
#关闭资源
fis.close()
except OSError as ioe:
print(ioe.strerror)
print("执行finally块中的资源回收")
#测试return,输出
No such file or directory
执行finally块中的资源回收
#测试os._exit(1),输出
No such file or directory
二、 使用raise抛出自定义异常
异常其实是很主观的东西,除了明确会导致程序错误的报错外,也需要结合具体业务确定。例如设置密码时小于6位抛出异常,这就不是python默认的报错,属于自定义异常。
1. raise语句
raise语句用于在程序中抛出自定义异常,通常有以下3种用法:
raise # 单独一个raise,抛出当前上下文中捕获的异常(例如except语句中),或默认抛出RuntimeError异常
raise 异常类 # 抛出指定异常类的默认实例
raise 异常对象 # 抛出指定异常对象
自定义异常类
自定义异常类都应该继承Exception类或其子类,定义时通常指定其父类即可。
class MyException(Exception):
pass
3种用法最终抛出的都是异常实例,raise语句每次只能抛出一个异常实例,若未在except中捕获,程序会直接报错退出
inputStr=input("请输入除数:")
try:
num=int(inputStr)
#若num>20则抛出自定义异常
if num>20:
#引发默认RuntimeError异常
raise
result=20/num
print("20除以%s的结果为:%g" % (inputStr,result))
except ValueError:
print("输入非数值,请输入一个非0数字")
except ArithmeticError:
print("输入值为0,请输入一个非0数字")
except RuntimeError:
print("num>20,抛出了自定义RuntimeError异常")
#测试输出
请输入除数:21
num>20,抛出了自定义RuntimeError异常
2. except与raise结合使用
实际应用中,当一个异常出现时,可能无法单靠某个方法进行处理,而要由几个方法协作,此时可以通过except与raise结合实现。一个常见的例子是,应用一方面需将报错记入日志文件,另一方面还需返回其他告知用户报错信息。
class MyException(Exception):
pass
class Bid:
#构造方法定义起拍价
def __init__(self,init_price):
self.init_price=init_price
#定义竞拍函数
def bid_func(self,bid_price):
d=0.0
try:
d=float(bid_price)
except Exception as e:
#此处只打印简单异常信息
print("类型转换出现异常",e)
#再次抛出自定义异常进行详细处理
raise MyException("竞拍价必须为数值")
if self.init_price>d:
raise MyException("竞拍价不得低于起拍价")
initPrice=d
def main():
test=Bid(21.6)
try:
test.bid_func("yyy")
test.bid_func(6)
except MyException as e2:
#再次捕获异常并进行处理
print("main函数中捕获的异常",e2)
main()
#测试输出
类型转换出现异常 could not convert string to float: 'yyy'
main函数中捕获的异常 竞拍价必须为数值
三、 异常传播轨迹
python提供了traceback模块处理异常传播轨迹,查看异常源头。
traceback模块提供了两个常用方法:
- format_exc():将异常传播轨迹转换为字符串
- traceback.print_exc():将异常传播轨迹输出到控制台或指定文件
print_exc()的完整形式是print_exception(etype,value,tb[,limit[,file]])
- etype:指定异常类型
- value:指定异常值
- tb:指定异常traceback信息
- limit:限制显示异常的传播层数,若不设置默认全部显示
- file:将异常传播轨迹输出到指定文件,若不设置则输出到控制台
import traceback
class MyException(Exception):
pass
def main():
first()
def first():
second()
def second():
third()
def third():
raise MyException("自定义异常信息")
try:
main()
except:
#捕获异常并输出至控制台
traceback.print_exc()
#捕获异常并输出至指定文件
traceback.print_exc(file=open('log.txt','a'))
#输出如下
Traceback (most recent call last):
File "E:/pytest02/var.py", line 19, in <module>
main()
File "E:/pytest02/var.py", line 7, in main
first()
File "E:/pytest02/var.py", line 10, in first
second()
File "E:/pytest02/var.py", line 13, in second
third()
File "E:/pytest02/var.py", line 16, in third
raise MyException("自定义异常信息")
MyException: 自定义异常信息
输出至文件结果
报错栈应该从下往上看,报错源头是在third方法第16行,对应的正好是抛出异常部分
若不使用该模块,也不捕获异常,python编辑器会利用自带的with_traceback在控制台打出报错栈。
class MyException(Exception):
pass
def main():
first()
def first():
second()
def second():
third()
def third():
raise MyException("自定义异常信息")
main()
#控制台报错信息
Traceback (most recent call last):
File "E:/pytest02/var.py", line 16, in <module>
main()
File "E:/pytest02/var.py", line 5, in main
first()
File "E:/pytest02/var.py", line 8, in first
second()
File "E:/pytest02/var.py", line 11, in second
third()
File "E:/pytest02/var.py", line 14, in third
raise MyException("自定义异常信息")
__main__.MyException: 自定义异常信息
四、 异常处理规则
- 不要过度使用异常
- 不要使用过于庞大的try块
- 不要忽略捕获到的异常