>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:25:13
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.6. 遵循异常处理的几点基本原则

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

try:

<statements> # Run the 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-dse-finally异常处理的流程如图所示

_images/3-1.png

3.6.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")

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

>>> import sys
>>> try:
>>>     print( a ,b =0)
>>>     print (a/b)
>>> except ZeroDivisionError:
>>>     sys.exit("ZeroDivisionError:Can not division zero")
>>>
>>> #运行程序输出NameError异常如下:
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-19-3e7e97d06070> in <module>()
      1 import sys
      2 try:
----> 3     print( a ,b =0)
      4     print (a/b)
      5 except ZeroDivisionError:


NameError: name 'a' is not defined

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

3)注意异常捕获的顺序,在合适的层次 处理异常。Python中内建异常以类的形式出现, Python2.5后异常被迁移到新式类上,启用r一个 新的所有异常之母的BaseException类,内建异常 有一定的继承结构,如UnicodeDecodeError继承自 UnicodeError,而其继承链结构为 UnicodeDecodeError —>UnicodcError —>ValueError –>Exception ->BaseEJtcepti〇n,如图 3-2 所示。 用户也可以继承自内建异常构建自己的异常 类,从而在内建类的继承结构上进一步延伸P在 这种情况下异常捕获的顺序显得非常重要。为了

_images/3-2.png

图3-2 UnkodeDecodeError继承结构示意阁

精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的except语句 中抛出,而父类异常在后面的except语句拋出。这样做的原因是当try块中有异常发生的时 候,解铎器根据except声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。如 果将S次较高的异常类在前面进行捕获,往往不能精确地定位异常发生的具体位置。如下例 中 ValueError 声明在前而 UnicodeDecodeError 在后,当拋出 UnicodeDecodeError 异常的时 候,由于它是ValueError的子类,在ValueError处便直接被捕获了,打印出消息“ ValueError occured ”,而真正的异常UnicodeDecodeError却被悄然掩盖。

try:

raise UnicodeDecodeError('pdfdocencoding', "a",2,-1, "not support decoding")

except ValueError:

#Value£rror 为 UnicodeDecocieError 的父类,捕获异常时却在前面 print( "ValueError occured")

except UnicodeDecodeError as e:

print(e)

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

try:

somecode()

except:

revert_stuff ()

raise

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

Traceback (most recent call last):

File ntest.py", line 4f in <module>

print ItemPriceTable['a']

KeyError: 'a'

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

>>> import sys
>>> import traceback
>>> ItemPriceTable ={ "apple":'3,5' ,'orangem':' 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 Jtind of fruit"%sys.exc_value) #屋示异常相关的提示信息
>>> while 1:
>>>     itemname = input("Enter the fruit 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))
Enter the fruit get the price or press x to exit:  mango
mango's price is $8/kg
Enter the fruit get the price or press x to exit:  x

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