返回介绍

建议24:遵循异常处理的几点基本原则

发布于 2024-01-30 22:19:09 字数 4514 浏览 0 评论 0 收藏 0

现实世界是不完美的,意外和异常会在不经意间发生,从而使我们的生活不得不暂时偏离正常轨道,软件世界也是如此。或因为外部原因,或因为内部原因,程序会在某些条件下产生异常或者错误。为了提高系统的健壮性和用户的友好性,需要一定的机制来处理这种情况。跟其他很多编程语言一样,Python也提供了异常处理机制。Python中常用的异常处理语法是try、except、else、finally,它们可以有多种组合,如try-except(一个或多个),try -except-else;try -finally以及try -except-else-finally等。语法形式如下:

try:
  <statements>      # Run this main action first
except <name1>:
  <statements>      # 
当 try
中发生name1
的异常时处理
except (name2, name3):
  <statements>      # 
当try
中发生name2
或name3
中的某一个异常的时候处理
except <name4> as <data>:
  <statements>      #
当 try
中发生name4
的异常时处理,并获取对应实例
except:
  <statements>      # 
其他异常发生时处理
else:
  <statements>      # 
没有异常发生时执行
finally:
 <statements>         #
不管有没有异常发生都会执行

最为全面的组合try -except-else-finally异常处理的流程如图3-1所示。

图3-1 异常处理的流程图

异常处理通常需要遵循以下几点基本原则:

1)注意异常的粒度,不推荐在try中放入过多的代码。异常的粒度是人为划分的,在处理异常的时候最好保持异常粒度的一致性和合理性,同时要避免在try中放入过多的代码,即避免异常粒度过大。在try中放入过多的代码带来的问题是如果程序中抛出异常,将会较难定位,给debug和修复带来不便,因此应尽量只在可能抛出异常的语句块前面放入try语句。

2)谨慎使用单独的except语句处理所有异常,最好能定位具体的异常。同样也不推荐使用except Exception或者except StandardError来捕获异常。

在try后面单独使用except语句可以捕获所有的异常,从表面上看这似乎是个不错的做法,但实际上会带来什么问题呢?来看以下简单的例子:

import sys
try:
  print a
  b =0
  print a/b
except:
  sys.exit("ZeroDivisionError:Can not division zero")

程序运行以打印“ZeroDivisionError:Can not division zero”结束,这会让我们以为是发生了除数为零的错误,但实际情况是因为a在使用前并没有定义,程序引发了NameError。而由于单独的except语句的使用,真实的错误往往被掩盖。对上述代码修改如下:

import sys
try:
  print a
  b =0
  print a/b
except ZeroDivisionError:
  sys.exit("ZeroDivisionError:Can not division zero")

运行程序输出NameError异常如下:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
  print a
NameError: name 'a' is not defined

单独使用except会捕获包括SystemExit,KeyboardInterrupt等在内的各种异常,从而掩盖程序真正发生异常的原因,给debug造成一定的迷惑性。因此需要谨慎使用,最好能在except语句中定位具体的异常。如果在某些情况下不得不使用单独的except语句,最好能够使用raise语句将异常抛出向上层传递。

3)注意异常捕获的顺序,在合适的层次处理异常。Python中内建异常以类的形式出现,Python2.5后异常被迁移到新式类上,启用了一个新的所有异常之母的BaseException类,内建异常有一定的继承结构,如UnicodeDecodeError继承自UnicodeError,而其继承链结构为UnicodeDecodeError -->UnicodeError -->ValueError -->Exception -->BaseException,如图3-2所示。

图3-2 UnicodeDecodeError继承结构示意图

用户也可以继承自内建异常构建自己的异常类,从而在内建类的继承结构上进一步延伸。在这种情况下异常捕获的顺序显得非常重要。为了更精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的except语句中抛出,而父类异常在后面的except语句抛出。这样做的原因是当try块中有异常发生的时候,解释器根据except声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。如果将层次较高的异常类在前面进行捕获,往往不能精确地定位异常发生的具体位置。如下例中ValueError声明在前而UnicodeDecodeError在后,当抛出UnicodeDecodeError异常的时候,由于它是ValueError的子类,在ValueError处便直接被捕获了,打印出消息“ValueError occured”,而真正的异常UnicodeDecodeError却被悄然掩盖。

>>> try:
...   raise UnicodeDecodeError("pdfdocencoding","a",2,-1,"not support decoding
")
... except ValueError:#ValueError
为UnicodeDecodeError
的父类,捕获异常时却在前面
...   print "ValueError occured"
... except UnicodeDecodeError,e:
...   print e
...
ValueError occured
>>>

因此,异常捕获的顺序非常重要,同时异常应该在适当的位置被处理,一个原则就是如果异常能够在被捕获的位置被处理,那么应该及时处理,不能处理也应该以合适的方式向上层抛出。遇到异常不论好歹就向上层抛出是非常不明智的。向上层传递的时候需要警惕异常被丢失的情况,可以使用不带参数的raise来传递。

try:
  some_code()
except:
  revert_stuff()
  raise

4)使用更为友好的异常信息,遵守异常参数的规范。软件最终是为用户服务的,当异常发生的时候,异常信息清晰友好与否直接关系到用户体验。通常来说有两类异常阅读者:使用软件的人和开发软件的人,即用户和开发者。对于用户来说关注更多的是业务。先来看一段异常信息:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
  print ItemPriceTable['a']
KeyError: 'a'

如果将这段信息给一个没有软件编程背景的人看,他肯定会觉得如读天书一般,对于用户这并不是一种较为友好的做法,在面对用户的时候异常信息应该以较为清晰和明确的方式显示出来。如下例中当查找一个不在列表的水果价格的时候给出相关的提示信息会比直接抛出KeyError信息要友好得多。

import sys
import  traceback
ItemPriceTable = {"apple":'3.5',"orange":'4',"cheery":"20","mango":"8"}
def getprice(itemname):
   try:
     price = ItemPriceTable[itemname]
     return price
   except KeyError:
     print "%s can not find in the price table,you should input another kind of fruit."
       % sys.exc_value # 
显示异常相关的提示信息
while 1:
  itemname = raw_input("Enter the fruit name to get the price or press x to exit: ")
  if itemname == "x":
    break
  price = getprice(itemname)
  if price != None: 
    print "%s's price is $%s/kg" % (itemname, price)

此外,如果内建异常类不能满足需求,用户可以在继承内建异常的基础上针对特定的业务逻辑定义自己的异常类。但无论是内建异常类,还是用户定义的异常类,在传递异常参数的时候都需要遵守异常参数规范。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文