Python C 扩展向异常添加属性
Python C Extensions adding Attributes to an Exception
我正在包装一个 C 库,它 returns 失败时的有限数量的错误代码之一。当错误发生时,我想将错误代码添加为 C 异常的属性,以便 Python 代码可以检索它并将错误代码映射到人类可读的异常。这可能吗?
例如,我想在 Python 层中执行此操作:
try:
call_my_library_func()
except MyLibraryError as ex:
print("Error code was %s" % ex.code)
我不喜欢的最接近的方法是使用 PyErr_SetObject
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyUnicode_FromString("Helpful error message"));
PyTuple_SetItem(tuple, 1, PyLong_FromLong(257));
//PyErr_SetString(MyLibraryError, "Helpful error message\n");
PyErr_SetObject(MyLibraryError, tuple);
那我可以这样做:
try:
call_my_library_func()
except MyLibraryError as ex:
message, code = ex.args[0], -1
if len(ex.args > 1):
code = ex.args[1]
C API 异常处理主要是根据 class、参数(传递给构造函数)及其回溯引发异常而编写的,因此可能最好按照那个计划。将元组作为参数传递的基本方法可能是最佳选择。
然而,有两个选项可以让您的例外 class 在 Python 方面稍微更加用户友好:
- 您在自定义
__init__
方法中处理参数以在 class 上设置 code
属性。
- 您将
code
定义为访问 args[1]
. 的异常 class 的 属性
我已经说明了选项 2,但我认为没有充分的理由偏爱其中之一。
简要解释下面的示例代码:要使用 C API 定义异常,您可以使用 PyErr_NewException
,它采用可选的基础 class 和字典作为其第二个和第三个参数.使用的函数(__init__
或 属性 定义)应该是字典的一部分。
为了定义 属性 定义,我在 Python 中编写了代码并使用了 PyRun_String
,因为在 Python 中编写比 C 更容易,因为我怀疑此代码对性能至关重要。这些函数最终注入到传递给 PyRun_String
.
的全局字典中
C代码:
#include <Python.h>
PyObject* make_getter_code() {
const char* code =
"def code(self):\n"
" try:\n"
" return self.args[1]\n"
" except IndexError:\n"
" return -1\n"
"code = property(code)\n"
"def message(self):\n"
" try:\n"
" return self.args[0]\n"
" except IndexError:\n"
" return ''\n"
"\n";
PyObject* d = PyDict_New();
PyObject* dict_globals = PyDict_New();
PyDict_SetItemString(dict_globals, "__builtins__", PyEval_GetBuiltins());
PyObject* output = PyRun_String(code,Py_file_input,dict_globals,d);
if (output==NULL) {
Py_DECREF(d);
return NULL;
}
Py_DECREF(output);
Py_DECREF(dict_globals);
return d;
}
static PyObject* MyLibraryError;
static PyObject* my_library_function(PyObject* self) {
/* something's gone wrong */
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyUnicode_FromString("Helpful error message"));
PyTuple_SetItem(tuple, 1, PyLong_FromLong(257));
PyErr_SetObject(MyLibraryError, tuple);
return NULL;
}
static PyMethodDef methods[] = {
{"my_library_function", my_library_function, METH_NOARGS,
"raise an error."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef librarymodule = {
PyModuleDef_HEAD_INIT,
"library", /* name of module */
NULL, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
methods
};
PyMODINIT_FUNC
PyInit_library(void) {
PyObject *m;
m = PyModule_Create(&librarymodule);
if (m == NULL)
return NULL;
PyObject* exc_dict = make_getter_code();
if (exc_dict == NULL) {
return NULL;
}
MyLibraryError = PyErr_NewException("library.MyLibraryError",
NULL, // use to pick base class
exc_dict);
PyModule_AddObject(m,"MyLibraryError",MyLibraryError);
return m;
}
作为更优雅的 Python 界面的示例,您的 Python 代码更改为:
try:
my_library_func()
except MyLibraryError as ex:
message, code = ex.message, ex.code
我正在包装一个 C 库,它 returns 失败时的有限数量的错误代码之一。当错误发生时,我想将错误代码添加为 C 异常的属性,以便 Python 代码可以检索它并将错误代码映射到人类可读的异常。这可能吗?
例如,我想在 Python 层中执行此操作:
try:
call_my_library_func()
except MyLibraryError as ex:
print("Error code was %s" % ex.code)
我不喜欢的最接近的方法是使用 PyErr_SetObject
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyUnicode_FromString("Helpful error message"));
PyTuple_SetItem(tuple, 1, PyLong_FromLong(257));
//PyErr_SetString(MyLibraryError, "Helpful error message\n");
PyErr_SetObject(MyLibraryError, tuple);
那我可以这样做:
try:
call_my_library_func()
except MyLibraryError as ex:
message, code = ex.args[0], -1
if len(ex.args > 1):
code = ex.args[1]
C API 异常处理主要是根据 class、参数(传递给构造函数)及其回溯引发异常而编写的,因此可能最好按照那个计划。将元组作为参数传递的基本方法可能是最佳选择。
然而,有两个选项可以让您的例外 class 在 Python 方面稍微更加用户友好:
- 您在自定义
__init__
方法中处理参数以在 class 上设置code
属性。 - 您将
code
定义为访问args[1]
. 的异常 class 的 属性
我已经说明了选项 2,但我认为没有充分的理由偏爱其中之一。
简要解释下面的示例代码:要使用 C API 定义异常,您可以使用 PyErr_NewException
,它采用可选的基础 class 和字典作为其第二个和第三个参数.使用的函数(__init__
或 属性 定义)应该是字典的一部分。
为了定义 属性 定义,我在 Python 中编写了代码并使用了 PyRun_String
,因为在 Python 中编写比 C 更容易,因为我怀疑此代码对性能至关重要。这些函数最终注入到传递给 PyRun_String
.
C代码:
#include <Python.h>
PyObject* make_getter_code() {
const char* code =
"def code(self):\n"
" try:\n"
" return self.args[1]\n"
" except IndexError:\n"
" return -1\n"
"code = property(code)\n"
"def message(self):\n"
" try:\n"
" return self.args[0]\n"
" except IndexError:\n"
" return ''\n"
"\n";
PyObject* d = PyDict_New();
PyObject* dict_globals = PyDict_New();
PyDict_SetItemString(dict_globals, "__builtins__", PyEval_GetBuiltins());
PyObject* output = PyRun_String(code,Py_file_input,dict_globals,d);
if (output==NULL) {
Py_DECREF(d);
return NULL;
}
Py_DECREF(output);
Py_DECREF(dict_globals);
return d;
}
static PyObject* MyLibraryError;
static PyObject* my_library_function(PyObject* self) {
/* something's gone wrong */
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyUnicode_FromString("Helpful error message"));
PyTuple_SetItem(tuple, 1, PyLong_FromLong(257));
PyErr_SetObject(MyLibraryError, tuple);
return NULL;
}
static PyMethodDef methods[] = {
{"my_library_function", my_library_function, METH_NOARGS,
"raise an error."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef librarymodule = {
PyModuleDef_HEAD_INIT,
"library", /* name of module */
NULL, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
methods
};
PyMODINIT_FUNC
PyInit_library(void) {
PyObject *m;
m = PyModule_Create(&librarymodule);
if (m == NULL)
return NULL;
PyObject* exc_dict = make_getter_code();
if (exc_dict == NULL) {
return NULL;
}
MyLibraryError = PyErr_NewException("library.MyLibraryError",
NULL, // use to pick base class
exc_dict);
PyModule_AddObject(m,"MyLibraryError",MyLibraryError);
return m;
}
作为更优雅的 Python 界面的示例,您的 Python 代码更改为:
try:
my_library_func()
except MyLibraryError as ex:
message, code = ex.message, ex.code