6-4-异常的设计
1. 嵌套异常处理器
try 语句可以嵌套,当 try/except 语句发生异常时,Python 会回到最近进入、具有相符 except 分句的 try 语句。一旦异常被捕捉,其生命就结束,控制权不会跳回所有匹配这个异常、相符的 try 语句;只有第一个 try 有机会对它进行处理。
当 try/finally 语句嵌套且发生异常时,每个 finally 代码块都会执行,Python 会持续把异常往上传递到其他 try 语句上,最终可能达到顶层默认处理器,除非有个 try/except 在这个过程中捕捉某处该异常。
1.1 例子:控制流程嵌套
1 | def action2(): |
inner try
1.2 例子:语法嵌套化
1 | try: |
inner try
嵌套处理器实际上是嵌入 try 代码块中,而不是写在其他被调用的函数中。
2. 异常的习惯用法
2.1 跳出多重嵌套循环:“go to”
异常经常被用来作为其他语言的“go to”语句来实现任意控制转移。raise 相当于“go to”,except 子句和异常名称作为程序标签。
1 | class Exitloop(Exception): pass |
loop3: 0
loop3: 1
loop3: 2
loop3: 3
continuing
如果使用 break 替换 raise,则会进入无限循环,因为 break 只能跳出一层循环。
2.2 异常不总是错误
内置的 input 函数在每次调用时,在文件末尾时引发内置的 EOFError。
调用 sys.exit() 并在键盘上按下 Ctrl-C,会分别引发 SystemExit 和 KeyboardInterrupt。
2.3 函数通过 raise 表示状态
用户定义的异常也可引发非错误的情况:
1 | class Found(Exception): pass |
2.4 关闭文件和服务器连接
2.5 在 try 外进行调试
1 | try: |
在错误发生后,仍保持程序处于激活状态,这样可以执行其他测试,而不用重新开始。
2.6 运行进程中的测试
1 | import sys |
2.7 关于 sys.exc_info
通常允许一个异常处理器获取对最近引发的异常的访问。如果没有处理器正在处理,就返回包含了三个 None 值的元组。否则,将会返回(type、value 和 traceback):
- type:正在处理的异常的异常类型
- value:引发的异常类实例
- traceback:代表异常最初发生时所调用的堆栈
sys.exc_info 主要由空的 except 使用。
2.8 显示错误和跟踪信息
1 | import traceback |
Bye
1 | !type badly.exc |
Traceback (most recent call last):
File "<ipython-input-6-b605ba4bc54e>", line 7, in <module>
inverse(0)
File "<ipython-input-6-b605ba4bc54e>", line 4, in inverse
return 1 / x
ZeroDivisionError: division by zero
3. 异常设计技巧和陷阱
3.1 应该包装什么
- 经常会失败的运算一般都应该包装在 try 语句内。例如,和系统状态衔接的运算(文件开启、套接字调用等)就是 try 的主要候选者。
- 在简单的脚本中,你会希望这类运算失败时终止程序,而不是被捕捉或是被忽略。Python 中的错误会产生有用的出错消息,而且这通常就是所期望的最好结果。
- 应该在 try/finally 中实现终止动作,从而保证它们的执行。
- 偶尔,把对大型函数的调用包装在单个 try 语句内,而不是让函数本身零散着放入若干 try 语句中。
3.2 捕捉太多:避免空 except 语句
3.3 捕捉过少:使用基于类的分类
1 | try: |
如果未来增加了 MyExcept3,就会视为错误并对其进行处理,除非更新异常列表。使用基于类的异常,可以让这种陷阱消失。如果捕捉一般的超类,就可以在未来新增和引发更为特定的子类,而不用手动扩展 except 分句的列表:超类会变成可扩展的异常分类。
异常处理器不要过于一般化,也不要过于太具体化,而且要明智选择 try 语句所包装的代码量。