Re: [capi-sig] Exceptions with additional instance variables

2008/12/22 Guilherme Polo <ggpolo@gmail.com>:
On Mon, Dec 22, 2008 at 10:06 AM, <chojrak11@gmail.com> wrote:
#include "Python.h"
static PyObject *MyErr;
static PyMethodDef module_methods[] = {
{"raise_test1", (PyCFunction)raise_test1, METH_NOARGS, NULL}, {"raise_test2", (PyCFunction)raise_test2, METH_NOARGS, NULL}, {"raise_test3", (PyCFunction)raise_test3, METH_NOARGS, NULL},
{NULL},
};
PyMODINIT_FUNC initfancy_exc(void) { PyObject *m;
m = Py_InitModule("fancy_exc", module_methods); if (m == NULL) return; MyErr = PyErr_NewException("fancy_exc.err", NULL, NULL); Py_INCREF(MyErr); if (PyModule_AddObject(m, "err", MyErr) < 0) return;
}
static PyObject * raise_test1(PyObject *self)
{ PyObject_SetAttrString(MyErr, "code", PyInt_FromLong(42)); PyObject_SetAttrString(MyErr, "category", PyString_FromString("nice one")); PyErr_SetString(MyErr, "All is good, I hope"); return NULL; }
static PyObject * raise_test2(PyObject *self)
{
PyObject *t = PyTuple_New(3); PyTuple_SetItem(t, 0, PyString_FromString("error message")); PyTuple_SetItem(t, 1, PyInt_FromLong(10)); PyTuple_SetItem(t, 2, PyString_FromString("category name here")); PyErr_SetObject(MyErr, t); Py_DECREF(t); return NULL;
}
In this second form you check for the args attribute of the exception.
static PyObject * raise_test3(PyObject *self) { PyObject *d = PyDict_New(); PyDict_SetItemString(d, "category", PyInt_FromLong(111)); PyDict_SetItemString(d, "message", PyString_FromString("error message")); PyErr_SetObject(MyErr, d); Py_DECREF(d); return NULL; }
(Small changes in the above code to be able to call more variants of raise_test methods simultaneously.)
Yes! I finally understood this (I think...) So to explain things for people like me:
PyErr_NewException creates *the class* in the module, it's a simple method of creating exception classes, but classes created that way are limited in features (i.e. cannot be manipulated from the module in all ways a 'full' type can). Third argument to PyErr_NewException can be NULL, in which case API will create an empty dictionary. After creating the class you need to add it to the module with PyModule_AddObject. Side note: If you want to specify a help for the class, you do PyObject_SetAttrString on the class with the key '__doc__'.
there's no instantiation anywhere: a. PyErr_SetString and PyErr_SetObject set the exception *class* (exception type) and exception data -- see http://docs.python.org/c-api/exceptions.html which notes that exceptions are similar in concept to the global 'errno' variable, so you just set what type of last error was and what error message (or other data) you want to associate with it b. the "code" and "category" variables from raise_test1() in the above example inserted with PyObject_SetAttrString() are *class* variables, not instance variables:
try: fancy_exc.raise_test1() except fancy_exc.err, e: print e.code, fancy_exc.err.code print fancy_exc.err.code
it prints: 42 42 42
c. the data is still present in the fancy_exc.err class after exception handling is finished, which is ok for now but may be problematic in case of multithreaded usage patterns (however I probably don't understand how multithreading in Python works)
- alternative to the above is to pass all required data to the exception with PyErr_SetObject - you can prepare a dictionary or a tuple earlier, which will be accessible with 'args' member:
try: fancy_exc.raise_test2() except fancy_exc.err, e: print e.args[0]
If it's dictionary, the syntax is a bit weird because e.args is always a tuple:
try: fancy_exc.raise_test3() except fancy_exc.err, e: print e.args[0]['category']
The 'args' values are unavailable outside of 'except' clause, however you can still use the 'e' variable which retains the values. So it's an instance variable.
creating the exception class using a new type in C (PyTypeObject structure) would give the most robust solution because every nuance of the class can be manipulated, but it's not worth the trouble now. I can switch to it transparently at a later time. Transparently means that nothing will need to be updated in Python solutions written by the module users.
most of the projects I've inspected with Google Code Search use the PyErr_NewException approach.
there's the option of using Cython which simplifies creating extensions and hides many unnecessary internals.
Many thanks Guilherme and Stefan for your help and for the patience.
Kind regards, Chojrak
participants (1)
-
chojrak11@gmail.com