关于错误和异常
整个 Python 解释器系统有一个如下所述的重要惯例:当一个函数运行失败时,它应当设置一个异常条件并返回一个错误值(通常为 -1 或 NULL 指针)。 异常信息保存在解释器线程状态的三个成员中。 如果没有异常则它们的值为 NULL。 在其他情况下它们是 sys.exc_info() 所返回的 Python 元组的成员的 C 对应物。 它们分别是异常类型、异常实例和回溯对象。 理解它们对于理解错误是如何被传递的非常重要。
Python API中定义了一些函数来设置这些变量。
最常用的就是 PyErr_SetString()。 其参数是异常对象和 C 字符串。 异常对象一般是像 PyExc_ZeroDivisionError 这样的预定义对象。 C 字符串指明异常原因,并被转换为一个 Python 字符串对象存储为异常的“关联值”。
另一个有用的函数是 PyErr_SetFromErrno() ,仅接受一个异常对象,异常描述包含在全局变量 errno 中。最通用的函数还是 PyErr_SetObject() ,包含两个参数,分别为异常对象和异常描述。你不需要使用 Py_INCREF() 来增加传递到其他函数的参数对象的引用计数。
你可以通过 PyErr_Occurred() 在不造成破坏的情况下检测是否设置了异常。 这将返回当前异常对象,或者如果未发生异常则返回 NULL。 你通常不需要调用 PyErr_Occurred() 来查看函数调用中是否发生了错误,因为你应该能从返回值中看出来。
当一个函数 f 调用另一个函数 g 时检测到后者出错了,f 应当自己返回一个错误值 (通常为 NULL 或 -1)。 它 不应 调用某个 PyErr_* 函数 --- 这类函数已经被 g 调用过了。 f 的调用者随后也应当返回一个错误来提示 它的 调用者,同样 不应 调用 PyErr_*,依此类推 --- 错误的最详细原因已经由首先检测到它的函数报告了。 一旦这个错误到达 Python 解释器的主循环,它会中止当前执行的 Python 代码并尝试找出由 Python 程序员所指定的异常处理句柄。
(在某些情况下模块确实能够通过调用其它 PyErr_* 函数来给出更为详细的错误消息,并且在这些情况下是可以这样做的。 但是按照一般规则,这是不必要的,并可能导致有关错误的信息丢失:大多数操作会由于种种原因而失败。)
想要忽略由一个失败的函数调用所设置的异常,异常条件必须通过调用 PyErr_Clear() 显式地被清除。 C 代码应当调用 PyErr_Clear() 的唯一情况是如果它不想将错误传给解释器而是想完全由自己来处理它(可能是尝试其他方法,或是假装没有出错)。
每次失败的 malloc() 调用必须转换为一个异常。 malloc() (或 realloc() )的直接调用者必须调用 PyErr_NoMemory() 来返回错误来提示。所有对象创建函数(例如 PyLong_FromLong() )已经这么做了,所以这个提示仅用于直接调用 malloc() 的情况。
还要注意的是,除了 PyArg_ParseTuple() 等重要的例外,返回整数状态码的函数通常都是返回正值或零来表示成功,而以 -1 表示失败,如同 Unix 系统调用一样。
最后,当你返回一个错误指示器时要注意清理垃圾(通过为你已经创建的对象执行 Py_XDECREF() 或 Py_DECREF() 调用)!
选择引发哪个异常完全取决于你的喜好。 所有 Python 内置异常都有对应的预声明 C 对象,例如 PyExc_ZeroDivisionError,你可以直接使用它们。 当然,你应当明智地选择异常 --- 不要使用 PyExc_TypeError 来表示文件无法打开(可能应该用 PyExc_OSError 比较好)。 如果参数列表有问题,PyArg_ParseTuple() 函数通常会引发 PyExc_TypeError。 如果你希望一个参数的值必须在特定范围内或必须满足其他条件,则适宜使用 PyExc_ValueError。
你也可以为你的模块定义一个唯一的新异常。需要在文件前部声明一个静态对象变量,如:
static PyObject *SpamError;
并在模块的初始化函数 (PyInit_spam()) 中附带异常对象对其进行初始化:
PyMODINIT_FUNC
PyInit_spam(void)
{
PyObject *m;
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
SpamError = PyErr_NewException("spam.error", NULL, NULL);
Py_XINCREF(SpamError);
if (PyModule_AddObject(m, "error", SpamError) < 0) {
Py_XDECREF(SpamError);
Py_CLEAR(SpamError);
Py_DECREF(m);
return NULL;
}
return m;
}
请注意该异常对象的 Python 名称为 spam.error。 PyErr_NewException() 函数可以创建基类为 Exception 的类 (除非传入了另一个类而不是 NULL),如 内置异常 中所描述的。
请注意 SpamError 变量保留了一个对新创建的异常类的引用;这是有意为之的! 由于异常可能会被外部代码从模块中删除,因此需要拥有一个对该类的引用以确保它不会被丢弃,从而导致 SpamError 成为一个悬空指针。 如果异常类成为悬空指针,则引发该异常的 C 代码可能会导致核心转储或其他预期之外的附带影响。
本样例稍后将讨论 PyMODINIT_FUNC 作为函数返回类型的用法。
可在扩展模块中调用 PyErr_SetString() 来引发 spam.error 异常,如下所示:
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < 0) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
}
return PyLong_FromLong(sts);
} |