向异常添加属性的Python C扩展
我正在包装一个C库,它在失败时返回有限数量的错误代码之一。当错误发生时,我希望将错误代码添加为C异常的一个属性,以便Python代码可以检索它并将错误代码映射到人类可读的异常。这可能吗 例如,我想在Python层中执行此操作:向异常添加属性的Python C扩展,python,python-3.x,python-c-api,Python,Python 3.x,Python C Api,我正在包装一个C库,它在失败时返回有限数量的错误代码之一。当错误发生时,我希望将错误代码添加为C异常的一个属性,以便Python代码可以检索它并将错误代码映射到人类可读的异常。这可能吗 例如,我想在Python层中执行此操作: try: call_my_library_func() except MyLibraryError as ex: print("Error code was %s" % ex.code) 我最接近的方法是使用PyErr\u SetObject PyObj
try:
call_my_library_func()
except MyLibraryError as ex:
print("Error code was %s" % ex.code)
我最接近的方法是使用PyErr\u 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异常处理在很大程度上是根据其类、参数(传递给构造函数)和回溯来引发异常的,因此最好遵循该方案。将元组作为参数传递的基本方法可能是最好的选择 但是,有两个选项可以使您的异常类在Python端稍微更为用户友好:
\uuuu init\uuu
方法中处理参数,以在类上设置code
属性code
定义为访问args[1]
的异常类的属性简要解释下面的示例代码:要使用C API定义异常,请使用
PyErr_NewException
,它将可选的基类和字典作为第二个和第三个参数。所使用的函数(无论是\uuuuu init\uuuu>还是属性定义)都应该是字典的一部分
为了定义属性定义,我已经用Python编写了代码,并使用了PyRun\u String
,因为用Python编写比用C更容易,而且我怀疑这段代码对性能至关重要。这些函数最终被注入到传递给PyRun\u String
的全局字典中
C代码:
#包括
PyObject*make_getter_code(){
常量字符*代码=
“def代码(自身):\n”
“请尝试:\n”
“返回self.args[1]\n”
“除索引器外:\n”
“返回-1\n”
“代码=属性(代码)\n”
“def消息(自身):\n”
“请尝试:\n”
“返回self.args[0]\n”
“除索引器外:\n”
返回“”\n
“\n”;
PyObject*d=PyDict_New();
PyObject*dict_globals=PyDict_New();
PyDict_SetItemString(dict_globals,“uuu内置项”,PyEval_GetBuiltins());
PyObject*output=PyRun\u字符串(代码,Py\u文件\u输入,dict\u全局,d);
if(输出==NULL){
Py_DECREF(d);
返回NULL;
}
Py_DECREF(输出);
Py_DECREF(dict_globals);
返回d;
}
静态PyObject*MyLibraryError;
静态PyObject*my_library_函数(PyObject*self){
/*出了点问题*/
PyObject*tuple=PyTuple\u New(2);
PyTuple_SetItem(tuple,0,PyUnicode_FromString(“有用的错误消息”);
PyTuple_SetItem(tuple,1,PyLong_from long(257));
PyErr_SetObject(MyLibraryError,元组);
返回NULL;
}
静态PyMethodDef方法[]={
{“我的库函数”,我的库函数,METH\u NOARGS,
“提出错误。”},
{NULL,NULL,0,NULL}/*哨兵*/
};
静态结构PyModuleDef librarymodule={
PyModuleDef_HEAD_INIT,
“库”、/*模块名称*/
NULL,/*模块文档可能为NULL*/
-1、/*模块每个解释器状态的大小,
如果模块在全局变量中保持状态,则为-1*/
方法
};
PyMODINIT_FUNC
PyInit_库(void){
PyObject*m;
m=PyModule\u创建(&librarymodule);
如果(m==NULL)
返回NULL;
PyObject*exc_dict=make_getter_code();
if(exc_dict==NULL){
返回NULL;
}
MyLibraryError=PyErr\u NewException(“library.MyLibraryError”,
NULL,//用于拾取基类
执行董事会);
PyModule_AddObject(m,“MyLibraryError”,MyLibraryError);
返回m;
}
作为更优雅的Python接口示例,您的Python代码更改为:
try:
my_library_func()
except MyLibraryError as ex:
message, code = ex.message, ex.code
您能否定义类MyLibraryError
,以便MyLibraryError.code
是一个返回args[1]
的属性?@DavidW当前MyLibraryError
在本例中是一个用C定义的异常。您是否建议创建一个Python异常,将该C异常作为子类,如class MyPyLibraryError(LibraryError)@property def code()返回getattr(self,'code',-1)
?我建议类MyPyLibraryError(LibraryError)@property def code(self):如果len(self.args)返回self.args[1]>=2 else-1
。如果MyLibraryError
是您创建的一个类,则可以在C中创建属性,而不是创建Python子类。@DavidW很好,然后在Python中我将执行尝试:调用\u my_library\u func(),MyLibraryError除外,例如:raise MyPylLibraryError(*ex.args)
对——最好将其添加为装饰器,并将其应用于所有my函数?不完全正确-在C中,您会引发MyPyLibraryError
(使用元组,就像您正在做的那样)。在Python中,您只需执行try:call\u my\u library\u func()除了MyPyLibraryError作为ex:code=ex.code#…和任何你想要的东西都很酷。我不知道PyRun_字符串
,也不知道在PyErr_NewException
期间设置dict。谢谢。我添加了一个编辑,显示了如何直接执行你最初要求的操作(在当前异常上设置一个属性)因此,避免与自定义类混淆。我个人仍然会使用自定义类,但关于您的附录,我必须执行PyObject\u SetAttrString(type,…)
,而不是值
。使用值
引发了一个属性错误:“str”对象没有属性“err”
。此外,对于PyErr\u还原
,有以下警告:“如果您不理解,请不要使用此函数。我警告过您。”-所以我会想