Python程序员只需要处理特定需要处理的错误异常;未处理的异常会自动传递给调用者,然后传递给调用者的调用者,依此类推,直到他们到达顶级解释器,在那里将它们报告给用户并伴随堆栈回溯。
然而,对于 C 程序员来说,错误检查必须总是显式进行的。 Python/C API 中的所有函数都可以引发异常,除非在函数的文档中另外显式声明。 一般来说,当一个函数遇到错误时,它会设置一个异常,丢弃它所拥有的任何对象引用,并返回一个错误标示。 如果没有说明例外的文档,这个标示将为 NULL 或 -1,具体取决于函数的返回类型。 有少量函数会返回一个布尔真/假结果值,其中假值表示错误。 有极少的函数没有显式的错误标示或是具有不明确的返回值,并需要用 PyErr_Occurred() 来进行显式的检测。 这些例外总是会被明确地记入文档中。
异常状态是在各个线程的存储中维护的(这相当于在一个无线程的应用中使用全局存储)。 一个线程可以处在两种状态之一:异常已经发生,或者没有发生。 函数 PyErr_Occurred() 可以被用来检查此状态:当异常发生时它将返回一个借入的异常类型对象的引用,在其他情况下则返回 NULL。 有多个函数可以设置异常状态: PyErr_SetString() 是最常见的(尽管不是最通用的)设置异常状态的函数,而 PyErr_Clear() 可以清除异常状态。
完整的异常状态由三个对象组成 (它为都可以为 NULL): 异常类型、相应的异常值,以及回溯信息。 这些对象的含义与 Python 中 sys.exc_info() 的结果相同;然而,它们并不是一样的:Python 对象代表由 Python try ... except 语句所处理的最后一个异常,而 C 层级的异常状态只在异常被传入到 C 函数或在它们之间传递时存在直至其到达 Python 字节码解释器的主事件循环,该事件循环会负责将其转移至 sys.exc_info() 等处。
请注意自 Python 1.5 开始,从 Python 代码访问异常状态的首选的、线程安全的方式是调用函数 sys.exc_info(),它将返回 Python 代码的分线程异常状态。 此外,这两种访问异常状态的方式的语义都发生了变化因而捕获到异常的函数将保存并恢复其线程的异常状态以保留其调用方的异常状态。 这将防止异常处理代码中由一个看起来很无辜的函数覆盖了正在处理的异常所造成的常见错误;它还减少了在回溯由栈帧所引用的对象的往往不被需要的生命其延长。
作为一般的原则,一个调用另一个函数来执行某些任务的函数应当检查被调用的函数是否引发了异常,并在引发异常时将异常状态传递给其调用方。 它应当丢弃它所拥有的任何对象引用,并返回一个错误标示,但它 不应 设置另一个异常 --- 那会覆盖刚引发的异常,并丢失有关错误确切原因的重要信息。
上面的 sum_sequence() 示例是一个检测异常并将其传递出去的简单例子。 碰巧的是这个示例在检测到错误时不需要清理所拥有的任何引用。 下面的示例函数展示了一些错误清理操作。 首先,为了提醒你 Python 的受欢迎程度,我们展示了等价的 Python 代码:
def incr_item(dict, key):
try:
item = dict[key]
except KeyError:
item = 0
dict[key] = item + 1
下面是对应的闪耀荣光的 C 代码:
int
incr_item(PyObject *dict, PyObject *key)
{
/* Objects all initialized to NULL for Py_XDECREF */
PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
int rv = -1; /* Return value initialized to -1 (failure) */
item = PyObject_GetItem(dict, key);
if (item == NULL) {
/* Handle KeyError only: */
if (!PyErr_ExceptionMatches(PyExc_KeyError))
goto error;
/* Clear the error and use zero: */
PyErr_Clear();
item = PyLong_FromLong(0L);
if (item == NULL)
goto error;
}
const_one = PyLong_FromLong(1L);
if (const_one == NULL)
goto error;
incremented_item = PyNumber_Add(item, const_one);
if (incremented_item == NULL)
goto error;
if (PyObject_SetItem(dict, key, incremented_item) < 0)
goto error;
rv = 0; /* Success */
/* Continue with cleanup code */
error:
/* Cleanup code, shared by success and failure path */
/* Use Py_XDECREF() to ignore NULL references */
Py_XDECREF(item);
Py_XDECREF(const_one);
Py_XDECREF(incremented_item);
return rv; /* -1 for error, 0 for success */
}
这个例子代表了 C 语言中 goto 语句一种受到认可的用法! 它说明了如何使用 PyErr_ExceptionMatches() 和 PyErr_Clear() 来处理特定的异常,以及如何使用 Py_XDECREF() 来处理可能为 NULL 的自有引用(注意名称中的 'X';Py_DECREF() 在遇到 NULL 引用时将会崩溃)。 重要的一点在于用来保存自有引用的变量要被初始化为 NULL 才能发挥作用;类似地,建议的返回值也要被初始化为 -1 (失败) 并且只有在最终执行的调用成功后才会被设置为成功。 |