在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题。重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_DECREF。在编写C语言代码时,需要了解Python提供的C/C++ API的实现细节,特别是有的API内部实现会调用Py_INCREF,这时自己编写的函数可能需要调用Py_DECREF,而有的API内部实现只是borrowed reference,此时一般不应该调用Py_DECREF。
本节讨论在C扩展Python时,如何对异常和错误进行处理。Python解释器的实现中有一个重要的约定:当一个函数失败,它应该设置一个exception condition并返回一个错误值(通常是NULl指针)。异常是存放在解释器的一个全局变量中,如果这个变量是NULL,那么没有异常发生。另外一个全局变量存放的是跟异常相关的值,还有一个变量包含了stack traceback,记录了产生错误时的Python Code。这三个变量对应Python的sys模块的三个变量,sys.exc_type, sys.exc_value, sys.exc_traceback。在1.5版本之后,这三个变量用exc_info()代替了。
这三个全局的变量在C Python 源码中是存放在PyThreadState *_PyThreadState_Current这个结构体中的, 而_PyThreadState_Current是在pythonrun.c的Py_InitializeEx中初始化的。
PyThreadState结构体定义:
红色框中的三个变量就是error indicator。
常见的Python设置异常的接口
Python API定义了一系列的function来设置不同类型的异常,定义在Python源码中的Python/error.c中。
PyErr_SetString:
最常用的莫过于PyErr_SetString,函数的原型为:
函数的作用是设置Python解释器的全局error indicator。
参数分别为一个exception对象和一个描述异常的字符串。exception object通常是一个已经定义好的object, 不需要增加它的引用计数,在PyErr_SetString源码中已经调用了Py_XINCREF(exception)。
/*Predefined exceptions*/PyAPI_DATA(PyObject*) PyExc_BaseException;
PyAPI_DATA(PyObject*) PyExc_Exception;
PyAPI_DATA(PyObject*) PyExc_StopIteration;
PyAPI_DATA(PyObject*) PyExc_GeneratorExit;
PyAPI_DATA(PyObject*) PyExc_StandardError;
PyAPI_DATA(PyObject*) PyExc_ArithmeticError;
PyAPI_DATA(PyObject*) PyExc_LookupError;
PyAPI_DATA(PyObject*) PyExc_AssertionError;
PyAPI_DATA(PyObject*) PyExc_AttributeError;
PyAPI_DATA(PyObject*) PyExc_EOFError;
PyAPI_DATA(PyObject*) PyExc_FloatingPointError;
PyAPI_DATA(PyObject*) PyExc_EnvironmentError;
PyAPI_DATA(PyObject*) PyExc_IOError;
PyAPI_DATA(PyObject*) PyExc_OSError;
PyAPI_DATA(PyObject*) PyExc_ImportError;
PyAPI_DATA(PyObject*) PyExc_IndexError;
PyAPI_DATA(PyObject*) PyExc_KeyError;
PyAPI_DATA(PyObject*) PyExc_KeyboardInterrupt;
PyAPI_DATA(PyObject*) PyExc_MemoryError;
PyAPI_DATA(PyObject*) PyExc_NameError;
PyAPI_DATA(PyObject*) PyExc_OverflowError;
PyAPI_DATA(PyObject*) PyExc_RuntimeError;
PyAPI_DATA(PyObject*) PyExc_NotImplementedError;
PyAPI_DATA(PyObject*) PyExc_SyntaxError;
PyAPI_DATA(PyObject*) PyExc_IndentationError;
PyAPI_DATA(PyObject*) PyExc_TabError;
PyAPI_DATA(PyObject*) PyExc_ReferenceError;
PyAPI_DATA(PyObject*) PyExc_SystemError;
PyAPI_DATA(PyObject*) PyExc_SystemExit;
PyAPI_DATA(PyObject*) PyExc_TypeError;
PyAPI_DATA(PyObject*) PyExc_UnboundLocalError;
PyAPI_DATA(PyObject*) PyExc_UnicodeError;
PyAPI_DATA(PyObject*) PyExc_UnicodeEncodeError;
PyAPI_DATA(PyObject*) PyExc_UnicodeDecodeError;
PyAPI_DATA(PyObject*) PyExc_UnicodeTranslateError;
PyAPI_DATA(PyObject*) PyExc_ValueError;
PyAPI_DATA(PyObject*) PyExc_ZeroDivisionError;
PyErr_SetObject:
从PyErr_SetString实现的源码中可以看到,内部调用了PyErr_SetObject, PyErr_SetObject内部又调用了PyErr_Restore。
PyErr_SetObject函数原型是:
PyObject* PyErr_Occurred():
函数返回当前是否有异常发生,如果有返回current exception object【borrowed reference】,否则返回NULL
int PyErr_ExceptionMatches(PyObject* exc)
int PyErr_GivenExceptionMatches(PyObject* given, PyObject* exc):
判断给定的异常对象是否符合exc类型。
PyErr_Clear():
清除Python解释器的error indicator。Python解释器不会检测到有异常发生。
PyErr_Fetch(PyObject** ptype, PyObject** pvalue, PyObject** ptraceback)
获得error indicator的三个变量,如果error indicator没有设置,ptype, pvalue, ptraceback都被设置为NULL。error indicator会被置空,将三个变量的地址赋给ptype, pvalue, ptraceback。
PyErr_Restore(PyObject* type, PyObject* value, PyObject* traceback)
使用给定的三个变量设置error indicator。
代码中使用异常接口的规则
如果函数f调用函数g,其中g函数失败,应该怎样设置异常呢?通常的做法是在g中调用设置异常的各个接口,比如PyErr_SetString,通知Python解释器有异常发生了,函数g然后返回一个NULL给函数f,而f不用再处理异常,因为函数g已经上报过了。比如我们自己写的函数调用了PyArg_ParseTuple(),这个函数出现错误时返回NULL,但是我们不用自己上报异常,异常的上报由PyArg_ParseTuple本身处理。一旦通过PyErr_SetString设置了异常,那么Python解释器在主循环中检测到error indicator被设置,会暂停执行当前的Python Code,会试图寻找exception handler来处理异常。
Python源码中使用异常处理接口的例子
PyTuple_GetItem:
PyObject *PyTuple_GetItem(register PyObject*op, register Py_ssize_t i)
{if (!PyTuple_Check(op)) {
PyErr_BadInternalCall();returnNULL;
}if (i < 0 || i >=Py_SIZE(op)) {//如果索引有错误,设置异常
PyErr_SetString(PyExc_IndexError, "tuple index out of range");returnNULL;
}return ((PyTupleObject *)op) ->ob_item[i];
}
PyErr_SetString实现:
voidPyErr_SetString(PyObject*exception, const char *string)
{
PyObject*value = PyString_FromString(string);
PyErr_SetObject(exception, value);
Py_XDECREF(value);
}
PyErr_SetObject实现:
voidPyErr_SetObject(PyObject*exception, PyObject *value)
{
Py_XINCREF(exception);//增加引用计数
Py_XINCREF(value); //增加引用计数
PyErr_Restore(exception, value, (PyObject *)NULL);
}
PyErr_Restore实现:
voidPyErr_Restore(PyObject*type, PyObject *value, PyObject *traceback)
{
PyThreadState*tstate =PyThreadState_GET();
PyObject*oldtype, *oldvalue, *oldtraceback;if (traceback != NULL && !PyTraceBack_Check(traceback)) {/*XXX Should never happen -- fatal error instead?*/
/*Well, it could be None.*/Py_DECREF(traceback);
traceback=NULL;
}/*Save these in locals to safeguard against recursive
invocation through Py_XDECREF*/oldtype= tstate->curexc_type;
oldvalue= tstate->curexc_value;
oldtraceback= tstate->curexc_traceback;//设置当前的异常
tstate->curexc_type =type;
tstate->curexc_value =value;
tstate->curexc_traceback =traceback;//旧的异常变量需要减少引用计数
Py_XDECREF(oldtype);
Py_XDECREF(oldvalue);
Py_XDECREF(oldtraceback);
}
自定义异常
除了使用Python已经定义好的异常对象之外,我们可以自定义异常类型,主要是通过PyErr_NewException创建一个异常对象。
1. 在文件头部定义一个static 变量:
static PyObject *MyError;
2. 在模块初始化时传入我们自定义的异常对象
PyMODINIT_FUNC
inittest(void)
{
PyObject*m;
m= Py_InitModule("test", TestMethods);if (m ==NULL)return;
MyError= PyErr_NewException("test.error", NULL, NULL);
Py_INCREF(MyError);
PyModule_AddObject(m,"error",MyError);
}
3. 在通过PyErr_SetString设置异常时,第一个参数传入MyError即可。