Python C 扩展向异常添加属性

Mat*_*sen 5 python python-c-api python-3.x

我正在包装一个 C 库,该库在失败时返回有限数量的错误代码之一。当错误发生时,我想将错误代码添加为 C 异常的属性,以便 Python 代码可以检索它并将错误代码映射到人类可读的异常。这可能吗?

例如我想在Python层执行此操作:

try:
    call_my_library_func()
except MyLibraryError as ex:
    print("Error code was %s" % ex.code)
Run Code Online (Sandbox Code Playgroud)

我能达到的最接近但我不喜欢的方法是使用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);
Run Code Online (Sandbox Code Playgroud)

然后我可以这样做:

try:
    call_my_library_func()
except MyLibraryError as ex:
    message, code = ex.args[0], -1
    if len(ex.args > 1):
        code = ex.args[1]
Run Code Online (Sandbox Code Playgroud)

Dav*_*idW 5

C API 异常处理主要是通过其类、其参数(传递给构造函数)及其回溯来引发异常,因此最好遵循该方案。将元组作为参数传递的基本方法可能是最好的选择。

然而,有两个选项可以使您的异常类在 Python 方面更加用户友好:

  1. 您可以在自定义方法中处理参数__init__以设置code类的属性。
  2. 您定义code为访问 的异常类的属性args[1]

我已经说明了选项 2,但我认为没有充分的理由选择其中之一。


简要解释下面的示例代码:使用您使用的 C API 定义异常,PyErr_NewException该 API 将可选的基​​类和字典作为其第二个和第三个参数。使用的函数(或者__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;
}
Run Code Online (Sandbox Code Playgroud)

作为更优雅的 Python 界面的示例,您的 Python 代码更改为:

try:
    my_library_func()
except MyLibraryError as ex:
    message, code = ex.message, ex.code
Run Code Online (Sandbox Code Playgroud)