[Python-Dev] [capi-sig] Exceptions with additional instance variables (original) (raw)

chojrak11 at gmail.com chojrak11 at gmail.com
Mon Dec 22 22:21:21 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 modulemethods[] = { {"raisetest1", (PyCFunction)raisetest1, METHNOARGS, NULL}, {"raisetest2", (PyCFunction)raisetest2, METHNOARGS, NULL}, {"raisetest3", (PyCFunction)raisetest3, METHNOARGS, NULL}, {NULL}, }; PyMODINITFUNC initfancyexc(void) { PyObject *m; m = PyInitModule("fancyexc", modulemethods); if (m == NULL) return; MyErr = PyErrNewException("fancyexc.err", NULL, NULL); PyINCREF(MyErr); if (PyModuleAddObject(m, "err", MyErr) < 0) return; } static PyObject * raisetest1(PyObject *self) { PyObjectSetAttrString(MyErr, "code", PyIntFromLong(42)); PyObjectSetAttrString(MyErr, "category", PyStringFromString("nice one")); PyErrSetString(MyErr, "All is good, I hope"); return NULL; } static PyObject * raisetest2(PyObject *self) { PyObject *t = PyTupleNew(3); PyTupleSetItem(t, 0, PyStringFromString("error message")); PyTupleSetItem(t, 1, PyIntFromLong(10)); PyTupleSetItem(t, 2, PyStringFromString("category name here")); PyErrSetObject(MyErr, t); PyDECREF(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)

  1. 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.

  1. 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.

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

  3. 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 Python-Dev mailing list