代码之家  ›  专栏  ›  技术社区  ›  Matthew Moisen

Python C扩展向异常添加属性

  •  0
  • Matthew Moisen  · 技术社区  · 7 年前

    我正在包装一个C库,它在失败时返回有限个错误代码中的一个。当错误发生时,我想在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]
    
    0 回复  |  直到 7 年前
        1
  •  4
  •   DavidW    6 年前

    C API异常处理在很大程度上是根据其类、参数(传递给构造函数)和回溯来引发异常的,因此最好遵循该方案。将元组作为参数传递的基本方法可能是最好的选择。

    但是,有两个选项可以让您的异常类在Python端稍微更加用户友好:

    1. 您可以在自定义文件中处理参数 __init__ 方法来设置 code 属性设置在类上。
    2. 你定义 密码 作为访问 args[1] .

    我已经说明了选项2,但我不认为有很大的理由选择其中一个。


    简要解释下面的示例代码:使用您使用的C API定义异常 PyErr_NewException 它将可选的基类和字典作为第二个和第三个参数。使用的函数(或 __初始化__ 或属性定义)应该是字典的一部分。

    为了定义属性定义,我用Python编写了代码,并使用 PyRun_String 因为Python比C更容易编写,而且我怀疑这段代码对性能至关重要。这些函数最终被注入传递给的全局字典中 皮伦_弦 .

    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