第三章 计算机程序的构造和解释 - 3.4 异常

优质
小牛编辑
125浏览
2023-12-01

程序员必须总是留意程序中可能出现的错误。例子数不胜数:一个函数可能不会收到它预期的信息,必需的资源可能会丢失,或者网络上的连接可能丢失。在设计系统时,程序员必须预料到可能产生的异常情况并且采取适当地措施来处理它们。

处理程序中的错误没有单一的正确方式。为提供一些持久性服务而设计的程序,例如 Web 服务器 应该对错误健壮,将它们记录到日志中为之后考虑,而且在尽可能长的时间内继续接受新的请求。另一方面,Python 解释器通过立即终止以及打印错误信息来处理错误,便于程序员在错误发生时处理它。在任何情况下,程序员必须决定程序如何对异常条件做出反应。

异常是这一节的话题,它为程序的错误处理提供了通用的机制。产生异常是一种技巧,终止程序正常执行流,发射异常情况产生的信号,并直接返回到用于响应异常情况的程序的封闭部分。Python 解释器每次在检测到语句或表达式错误时抛出异常。用户也可以使用或assert语句来抛出异常。

抛出异常。异常是一个对象实例,它的类直接或间接继承自BaseException类。第一章引入的assert语句产生AssertionError类的异常。通常,异常实例可以使用raise语句来抛出。raise语句的通用形式在 Python 文档中描述。raise的最常见的作用是构造异常实例并抛出它。

处理异常。异常可以使用封闭的try语句来处理。try语句由多个子句组成,第一个子句以try开始,剩下的以except开始。

  1. try:
  2. <try suite>
  3. except <exception class> as <name>:
  4. <except suite>
  5. ...

try语句执行时,<try suite>总是会立即执行。except子句组只在<try suite>执行过程中的异常产生时执行。每个except子句指定了需要处理的异常的特定类。例如,如果<exception class>AssertionError,那么任何继承自AssertionError的类实例都会被处理,标识符<name>绑定到所产生的异常对象上,但是这个绑定在<except suite>之外并不有效。

例如,我们可以使用try语句来处理异常,在异常发生时将x绑定为。

try语句能够处理产生在函数体中的异常,函数在<try suite>中调用。当异常产生时,控制流会直接跳到最近的try语句的能够处理该异常类型的<except suite>的主体中。

  1. >>> def invert(x):
  2. result = 1/x # Raises a ZeroDivisionError if x is 0
  3. print('Never printed if x is 0')
  4. return result
  5. try:
  6. return invert(x)
  7. except ZeroDivisionError as e:
  8. return str(e)
  9. >>> invert_safe(2)
  10. Never printed if x is 0
  11. 0.5
  12. >>> invert_safe(0)
  13. 'division by zero'

这个例子表明,invert中的print表达式永远不会求值,反之,控制流跳到了handler中的except子句组中。将ZeroDivisionError e强制转为字符串会得到由handler: 'division by zero'返回的人类可读的字符串。

异常对象本身就带有属性,例如在assert语句中的错误信息,以及有关异常产生处的信息。用户定义的异常类可以携带额外的属性。

首先,我们定义了新的类,继承自Exception

下面,我们定义了IterImprove,我们的通用迭代改进算法的一个版本。这个版本通过抛出IterImproveError异常,储存最新的猜测值来处理任何。像之前一样,iter_improve接受两个函数作为参数,每个函数都接受单一的数值参数。update函数返回新的猜测值,而done函数返回布尔值,表明改进是否收敛到了正确的值。

  1. >>> def iter_improve(update, done, guess=1, max_updates=1000):
  2. try:
  3. while not done(guess) and k < max_updates:
  4. guess = update(guess)
  5. k = k + 1
  6. return guess
  7. except ValueError:
  8. raise IterImproveError(guess)

最后,我们定义了find_root,它返回iter_improve的结果。iter_improve应用于由newton_update返回的牛顿更新函数。newton_update定义在第一章,在这个例子中无需任何改变。find_root的这个版本通过返回它的最后一个猜测之来处理IterImproveError

考虑使用find_root来寻找2 * x ** 2 + sqrt(x)的零点。这个函数的一个零点是0,但是在任何负数上求解它会产生ValueError。我们第一章的牛顿法实现会产生异常,并且不能返回任何零点的猜测值。我们的修订版实现在错误之前返回了最新的猜测值。

  1. >>> from math import sqrt
  2. >>> find_root(lambda x: 2*x*x + sqrt(x))
  3. -0.030211203830201594

虽然这个近似值仍旧距离正确的答案0很远,一些应用更倾向于这个近似值而不是ValueError

异常是另一个技巧,帮助我们将程序细节划分为模块化的部分。在这个例子中,Python 的异常机制允许我们分离迭代改进的逻辑,它在子句组中没有发生改变,以及错误处理的逻辑,它出现在except子句中。我们也会发现,异常在使用 Python 实现解释器时是个非常实用的特性。