[capi-sig] Exceptions with additional instance variables

chojrak11 at gmail.com chojrak11 at gmail.com
Mon Dec 22 22:24:45 CET 2008


2008/12/22 Guilherme Polo <ggpolo at gmail.com>:

> On Mon, Dec 22, 2008 at 10:06 AM,  <chojrak11 at 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:

1) 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__'.

2) 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)

3) 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.

4) 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.

5) most of the projects I've inspected with Google Code Search use the
PyErr_NewException approach.

6) 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


More information about the capi-sig mailing list