前面介绍了使用异常处理的优势、便捷之处,本节将进一步从程序性能优化、结构优化的角度给出异常处理的一般规则。
成功的异常处理应该实现如下 4 个目标:
下面介绍达到这些效果的基本准则。
不可否认,Python 的异常机制确实方便,但滥用异常机制也会带来一些负面影响。过度使用异常主要表现在两个方面:
熟悉了异常使用方法后,程序员可能不再愿意编写烦琐的错误处理代码,而是简单地引发异常。实际上这样做是不对的,对于完全己知的错误和普通的错误,应该编写处理这种错误的代码,增加程序的健壮性。只有对于外部的、不能确定和预知的运行时错误才使用异常。
对比前面五子棋游戏中,处理用户输入坐标点己有棋子的两种方式。如果用户试图下棋的坐标点己有棋子:
#如果要下棋的点不为空
if board[int(y_str) - 1) [int(x_str) - 1] !="╋" :
inputStr = input ("您输入的坐标点己有棋子了,请重新输入\n")
continue
上面这种处理方式检测到用户试图下棋的坐标点己经有棋子,立即打印一条提示语句,并重新开始下一次循环。这种处理方式简洁明了、逻辑清晰,程序的运行效率也很好程序进入 if 块后,即结束了本次循环。
如果将上面的处理机制改为如下方式:
#如果要下棋的点不为空
if board[int(y_str) - 1) [int(x_str) - 1) != "╋":
#引发默认的RuntimeError 异常
raise
上面这种处理方式没有提供有效的错误处理代码,当程序检测到用户试图下棋的坐标点己经有棋子时,并没有提供相应的处理,而是简单地引发一个异常。这种处理方式虽然简单,但 Python 解释器接收到这个异常后,还需要进入相应的 except 块来捕获该异常,所以运行效率要差一些。而且用户下棋重复这个错误完全是可预料的,所以程序完全可以针对该错误提供相应的处理,而不是引发异常。
另外,异常机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。例如,对于如下代码:
#定义一个字符串列表
my_list =["Hello", "Python", "Spring"]
#使用异常处理来遍历arr数组的每个元素
try:
i = 0
while True:
print (my_list [i])
i += 1
except:
pass
运行上面程序确实可以实现遍历 my_list 列表的功能,但这种写法可读性较差,而且运行效率也不高。程序完全有能力避免产生 indexError 异常,程序“故意”制造这种异常,然后使用 except 块去捕获该异常,这是不应该的。将程序改为如下形式肯定要好得多:
i = 0
while i < len(my_list):
print(my_list[i])
i += 1
注意,异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。对于一些完全可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常。
很多初学异常机制的读者喜欢在 try 块里放置大量的代码,这看上去很“简单”,但这种“简单”只是一种假象,只是在编写程序时看上去比较简单。但因为 try 块里的代码过于庞大,业务过于复杂,就会造成 try 块中出现异常的可能性大大增加,从而导致分析异常原因的难度也大大增加。
而且当时块过于庞大时,就难免在 try 块后紧跟大量的 except 块才可以针对不同的异常提供不同的处理逻辑。在同一个 try 块后紧跟大量的 except 块则需要分析它们之间的逻辑关系,反而增加了编程复杂度。
正确的做法是,把大块的 try 块分割成多个可能出现异常的程序段落,并把它们放在单独的 try 块中,从而分别捕获并处理异常。
不要忽略异常!既然己捕获到异常,那么 except 块理应做些有用的事情,及处理并修复异常。except 块整个为空,或者仅仅打印简单的异常信息都是不妥的!
except 块为空就是假装不知道甚至瞒天过海,这是最可怕的事情,程序出了错误,所有人都看不到任何异常,但整个应用可能已经彻底坏了。仅在 except 块里打印异常传播信息稍微好一点,但仅仅比空白多了几行异常信息。通常建议对异常采取适当措施,比如: