The C API exception handling is largely written in terms of raising an exception by its class, its arguments (passed to the constructor) and its traceback, and therefore those it's probably best to follow that scheme. Your basic approach of passing a tuple as the arguments is probably the best option.
However there are two options to make your exception class slightly more user-friendly on the Python side:
- You process the arguments in a custom
__init__ method to set a code attribute on the class.
- You define
code as a property of your exception class that accesses args[1].
I've illustrated option 2, but I don't think there a huge reason to prefer one or the other.
To briefly explain the example code below: to define an exception using the C API you use PyErr_NewException which takes an optional base class and dictionary as its second and third arguments. The functions used (either __init__ or the property definitions) should be part of the dictionary.
To define the property definitions I've written the code in Python and used PyRun_String since it's easier to write in Python than C and because I doubt this code will be performance critical. The functions end up injected into the global dictionary passed to PyRun_String.
C code:
#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;
}
As an example of the more elegant Python interface, your Python code changes to:
try:
my_library_func()
except MyLibraryError as ex:
message, code = ex.message, ex.code
MyLibraryErrorso thatMyLibraryError.codeis a property that returnsargs[1]?MyLibraryErrorin this example is an exception defined in C. Do you suggest creating a Python Exception which subclasses this C exception, likeclass MyPyLibraryError(LibraryError) @property def code() return getattr(self, 'code', -1)?class MyPyLibraryError(LibraryError) @property def code(self): return self.args[1] if len(self.args)>=2 else -1. IfMyLibraryErroris a class you've made then can create the property in C instead of making a Python subclass.try: call_my_library_func() except MyLibraryError as ex: raise MyPyLibraryError(*ex.args)right -- and better yet add it as a decorator and apply it to all my my functions?MyPyLibraryError(using the tuple, like you're doing). In Python you just dotry: call_my_library_func() except MyPyLibraryError as ex: code = ex.code # ... and whatever else you want