当程序出现错误时,系统会自动引发异常,除此之外,Python也允许程序自行引发异常,自行引发异常使用raise语句完成。
使用raise引发异常
如果在程序中需要自行引发异常,则应该使用raise语句,raise语句有如下三种常用的用法:
1. raise :单独一个raise
该语句引发现在上下文中捕获到的异常(如在except块中),或默认引发RuntimeError异常
2. raise 异常类 :raise后带一个异常类
该语句引发指定异常类的默认实例
3. raise 异常对象 :引发指定的异常对象
即使是用户自行引发的异常,也可以通过try...except进行捕获,当然也可以不管它,让该异常向上(先调用者)传播,如果该异常传到Python解释器,则程序就会中止。
如下示例示范了处理用户引发异常的两种方式:
由上面程序可以知道,程序即可在调用mtd(3)时使用try...except来捕获异常,这样改异常将会被except块捕获,不会传播给调用它的函数;也可直接调用mtd(3),这样该函数的异常就会直接传播给它的调用函数,如果该函数也不处理该异常,就会导致程序中止。
运行上面程序,输出结果如下:
上面第一行输出的是第一次调用mtd(3)的结果,该方法引发的异常被except块捕获并处理。
后面的大段输出是第二次调用mtd(3)的结果,由于该异常没有被except块捕获,因此该异常一直向上传播,直到传给Python解释器导致程序中止。
注意:第二次调用mtd(3)引发的以“File”开头的三行输出,其实现实的就是异常的传播轨迹信息,即如果程序不对异常进行处理,Python默认会在控制台输出异常的传播轨迹信息。
自定义异常类
用户自定义异常都应该继承Exception基类或Exception的子类,在自定义异常类时基本不需要写更多的代码,只需要指定自定义异常类的父类即可。
如下所示:
class AuctionException(Exception):
pass
上面程序创建了AuctionException异常类,该异常类不需要类体定义,因此使用pass语句作为占位符即可。
except和raise同时使用
在实际应用中对异常可能需要更复杂的处理方式——当一个异常出现时,单靠某个方法无法完成处理该异常,必须由几个方法协作才可完全处理该异常。即在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次引发异常,让该方法的调用者也能捕获异常。
为了实现这种通过多个方法协作处理同一个异常的情形,可在except块中结合raise语句完成,如下:
上面程序中AuctionTest类中except块捕获到异常后,系统打印了该异常的字符串信息,接着引发一个AuctionException异常,通知该方法的调用者再次处理该AuctionException异常。所以程序中的main()函数,也就是bid()方法的调用者还可再次捕获AuctionException异常,并将该异常的详细描述信息打印出来。
Python的异常传播轨迹
异常对象提供了一个with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹可追踪异常触发的源头,也可看到异常一路触发的轨迹。
如下示例:
上面程序中main()函数调用firstMethod(),firstMethod()调用secondMethod(),secondMethod()调用thirdMethod(),thirdMethod()直接引发一个SelfException异常。
运行上面程序,将会看到如下结果:
可以看出,异常从thirdMethod()函数开始触发,传到secondMethod()函数,再传到firstMethdo()函数,最后传到main()函数,在main()函数中止,这个过程就是Python的异常传播轨迹。
在实际应用程序的开发中,大多数可重用操作都会被分解成一系列函数或方法调用,这是因为为了具有更好的可重用性,会将每个可重用的代码单元定义成函数或方法,将复杂任务逐渐分解为更容易管理的小型子任务。由于一个大的业务功能需要由多个函数或方法共同实现,在最终编程模型中,很多对象将通过一系列函数或方法调用来实现通信,执行任务。
所以,当应用程序运行时,经常会发生一系列函数或方法调用,从而形成“函数调用栈”,异常的传播则相反:只要异常没有被完全捕获(包括异常没有被捕获,或异常被处理后重新引发了新异常),异常就从发生异常的函数或方法逐渐向外传播,首先传给该函数或方法的调用者,该函数或方法的调用者再传给其调用者......直至最后传到Python解释器,此时Python解释器会中止该程序,并打印异常的传播轨迹信息。
Python专门提供了traceback模块来处理异常传播轨迹,使用traceback可方便地处理Python的异常传播轨迹。
导入traceback模块之后,traceback提供了如下两个常用方法:
1. traceback.print_exc() :将异常传播轨迹信息输出到控制台或指定文件中
2. format_exc() :将异常传播轨迹信息转换成字符串
到这里可能你有疑问:从上面方法看不出它们到底处理哪个异常的传播轨迹信息?
OK,实际上常用的print_exc()是print_exc([limit[,file]])省略了limit、file两个参数的形式。
其中,limit用于限制显示异常传播的层数,比如函数A调用函数B,函数B发生了异常,如果指定limit=1,则只显示函数A里面发生的异常,如果不设置limit参数,则默认全部显示。
file指定将异常传播轨迹信息输出到指定文件中,如果不指定该参数,则默认输出到控制台。
示例如下:
上面程序中第一行先导入traceback模块,接下来程序使用except捕获程序的异常,并使用traceback的print_exc()方法输出异常传播信息,分别将它输出到控制台和指定文件中。
运行上面程序,同样可看到在控制台输出异常传播信息,且在程序目录下生成了一个log.txt文件,该文件中同样记录了异常传播信息。
最后说一下异常处理的规则:
一般来说,成功的异常处理应该事先如下4个目标:
1. 使程序代码混乱最小化
2. 捕获并保留诊断信息
3. 通知合适的人员
4. 采用合适的方式结束异常活动