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

On Mon, Dec 22, 2008 at 10:06 AM, <chojrak11@gmail.com> wrote:
On Mon, Dec 22, 2008 at 03:29, Guilherme Polo <ggpolo@gmail.com> wrote:
On Sun, Dec 21, 2008 at 11:02 PM, <chojrak11@gmail.com> wrote:
Hello,
I'm trying to implement custom exception that have to carry some useful info by means of instance members, to be used like:
try: // some code except MyException, data: // use data.errorcode, data.errorcategory, data.errorlevel, data.errormessage and some others
The question is - how to implement the instance variables with PyErr_NewException?
Using PyErr_NewException is fine. You must understand that an exception is a class, and thus PyErr_NewException creates one for you and returns it. Just like you would do with a class that has __dict__, set some attributes to what you want. That is, use PyObject_SetAttrString or something more appropriated for you.
Ok so I did the following. In init function (forget refcounting and error checking for a moment ;-)
PyObject *dict = PyDict_New(); PyDict_SetItemString(dict, "errorcode", PyInt_FromLong(0)); static PyObject *myexception = PyErr_NewException("module.MyException", NULL, dict);
You do not really have to create a dict here, one will be created for you if you pass a NULL there.
PyModule_AddObject(module, "MyException", myexception);
It worked more or less as expected, the help shown:
| ---------------------------------------------------------------------- | Data and other attributes defined here: | | errorcode = 0 | | ----------------------------------------------------------------------
Then I did the following when raising the exception:
PyObject_SetAttrString(myexception, "errorcode", PyInt_FromLong(111)); PyErr_SetString(myexception, "Bad thing happened"); return NULL;
and the test code was: try: do_bad_thing(); except MyException, data:
and you surely already guessed it -- data.errorcode was 0.... Not only that, module.MyException.errorcode was also 0...
What I'm doing wrong? I certainly don't get the idea of exceptions in Python, especially what is being raised - a class or an instance?
There are two forms raise can take, both will end up involving a class and a intsance.
If the latter - how's the class instantiated?
You can call a class to instantiate it.
If not - what about values in different threads? The docs are so vague about that...
Thanks again in advance, Chojrak
Again, an exception is a class, so you could create a new type in C, and do anything you wanted. But you probably don't want to create a new type to achieve this, so there are two simple ways I'm going to paste below: #include "Python.h" static PyObject *MyErr; static PyMethodDef module_methods[] = { {"raise_test", (PyCFunction)raise_test, 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; } the raise_test function is missing, pick one of these: static PyObject * raise_test(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; } or static PyObject * raise_test(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. -- -- Guilherme H. Polo Goncalves

On Mon, Dec 22, 2008 at 10:45 AM, Guilherme Polo <ggpolo@gmail.com> wrote:
On Mon, Dec 22, 2008 at 10:06 AM, <chojrak11@gmail.com> wrote:
On Mon, Dec 22, 2008 at 03:29, Guilherme Polo <ggpolo@gmail.com> wrote:
On Sun, Dec 21, 2008 at 11:02 PM, <chojrak11@gmail.com> wrote:
Hello,
I'm trying to implement custom exception that have to carry some useful info by means of instance members, to be used like:
try: // some code except MyException, data: // use data.errorcode, data.errorcategory, data.errorlevel, data.errormessage and some others
The question is - how to implement the instance variables with PyErr_NewException?
Using PyErr_NewException is fine. You must understand that an exception is a class, and thus PyErr_NewException creates one for you and returns it. Just like you would do with a class that has __dict__, set some attributes to what you want. That is, use PyObject_SetAttrString or something more appropriated for you.
Ok so I did the following. In init function (forget refcounting and error checking for a moment ;-)
PyObject *dict = PyDict_New(); PyDict_SetItemString(dict, "errorcode", PyInt_FromLong(0)); static PyObject *myexception = PyErr_NewException("module.MyException", NULL, dict);
You do not really have to create a dict here, one will be created for you if you pass a NULL there.
PyModule_AddObject(module, "MyException", myexception);
It worked more or less as expected, the help shown:
| ---------------------------------------------------------------------- | Data and other attributes defined here: | | errorcode = 0 | | ----------------------------------------------------------------------
Then I did the following when raising the exception:
PyObject_SetAttrString(myexception, "errorcode", PyInt_FromLong(111)); PyErr_SetString(myexception, "Bad thing happened"); return NULL;
and the test code was: try: do_bad_thing(); except MyException, data:
and you surely already guessed it -- data.errorcode was 0.... Not only that, module.MyException.errorcode was also 0...
What I'm doing wrong? I certainly don't get the idea of exceptions in Python, especially what is being raised - a class or an instance?
There are two forms raise can take, both will end up involving a class and a intsance.
If the latter - how's the class instantiated?
You can call a class to instantiate it.
If not - what about values in different threads? The docs are so vague about that...
Thanks again in advance, Chojrak
Again, an exception is a class, so you could create a new type in C, and do anything you wanted. But you probably don't want to create a new type to achieve this
By creating a type I mean one that involves defining a tp_init, and everything else your type needs, not about the simple one created by PyErr_NewException.
, so there are two simple ways I'm going to paste below:
#include "Python.h"
static PyObject *MyErr;
static PyMethodDef module_methods[] = { {"raise_test", (PyCFunction)raise_test, 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; }
the raise_test function is missing, pick one of these:
static PyObject * raise_test(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; }
or
static PyObject * raise_test(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.
-- -- Guilherme H. Polo Goncalves
-- -- Guilherme H. Polo Goncalves

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: 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

Not this list, sorry....
participants (2)
-
chojrak11@gmail.com
-
Guilherme Polo