在上一節中,討論了在用C語言擴展Python模塊時,應該如何處理無處不在的引用計數問題。重點關注的是在實現一個C Python的函數時,對於一個PyObject對象,什麼時候調用Py_INCREF和Py_DECREF。在編寫C語言代碼時,須要瞭解Python提供的C/C++ API的實現細節,特別是有的API內部實現會調用Py_INCREF,這時本身編寫的函數可能須要調用Py_DECREF,而有的API內部實現只是borrowed reference,此時通常不該該調用Py_DECREF。python
本節討論在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()代替了。app
這三個全局的變量在C Python 源碼中是存放在PyThreadState *_PyThreadState_Current這個結構體中的, 而_PyThreadState_Current是在pythonrun.c的Py_InitializeEx中初始化的。函數
PyThreadState結構體定義:spa
紅色框中的三個變量就是error indicator。指針
常見的Python設置異常的接口code
Python API定義了一系列的function來設置不一樣類型的異常,定義在Python源碼中的Python/error.c中。對象
PyErr_SetString:blog
最經常使用的莫過於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(); return NULL; } if (i < 0 || i >= Py_SIZE(op)) { // 若是索引有錯誤,設置異常 PyErr_SetString(PyExc_IndexError, "tuple index out of range"); return NULL; } return ((PyTupleObject *)op) -> ob_item[i]; }
PyErr_SetString實現:
void PyErr_SetString(PyObject *exception, const char *string) { PyObject *value = PyString_FromString(string); PyErr_SetObject(exception, value); Py_XDECREF(value); }
PyErr_SetObject實現:
void PyErr_SetObject(PyObject *exception, PyObject *value) { Py_XINCREF(exception); // 增長引用計數 Py_XINCREF(value); // 增長引用計數 PyErr_Restore(exception, value, (PyObject *)NULL); }
PyErr_Restore實現:
void PyErr_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便可。