Exception Handling in Pyrex

Contents

Intro

Patch by Stefan Behnel

References

Intro

11th November 2007

It has been said that one of Pyrex's strengths is that it inherits the full power of Python's exception catching. Unfortunately this statement became untrue with the advent of Python 2.5.

The reason for this is that under Python 2.5 exceptions have become new-style classes and the Pyrex compiler code which checks for valid exception types to be raised does not recognise them. This results in Python returning with an exception - not the expected one, of course, but a TypeError like this: TypeError: exceptions must be strings, classes or instances, not [whatever the exception raised was].

The prana leaks from the code at c function __Pyx_Raise found at line 3590 of Pyrex/Compiler/Node.py in the official release.

Patch by Stefan Behnel

11th November 2007

One of the warlocks of the Pyrex circle, Stefan Behnel, has re-written the __Pyx_Raise function in such a way that it works fine with Python 2.5's new-style exceptions.

While this patch has not been incorporated into the official Pyrex code, I've seen it perform well in the context of Project Ouroborus.

For those interested in trying it out, simply copy and paste into the corresponding Pyrex module.

static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
    Py_XINCREF(type);
    Py_XINCREF(value);
    Py_XINCREF(tb);
    /* First, check the traceback argument, replacing None with NULL. */
    if (tb == Py_None) {
        Py_DECREF(tb);
        tb = 0;
    }
    else if (tb != NULL && !PyTraceBack_Check(tb)) {
        PyErr_SetString(PyExc_TypeError,
            "raise: arg 3 must be a traceback or None");
        goto raise_error;
    }
    /* Next, replace a missing value with None */
    if (value == NULL) {
        value = Py_None;
        Py_INCREF(value);
    }
    /* Next, repeatedly, replace a tuple exception with its first item */
    while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
        PyObject *tmp = type;
        type = PyTuple_GET_ITEM(type, 0);
        Py_INCREF(type);
        Py_DECREF(tmp);
    }
    if (PyString_CheckExact(type)) {
        /* Raising builtin string is deprecated but still allowed --
         * do nothing.  Raising an instance of a new-style str
         * subclass is right out. */
        if (PyErr_Warn(PyExc_DeprecationWarning,
                   "raising a string exception is deprecated"))
                goto raise_error;
    }
    else if (PyType_Check(type) || PyClass_Check(type))
        ; /* PyErr_NormalizeException(&type, &value, &tb); */
    else if (PyInstance_Check(type)) {
        /* Raising an instance.  The value should be a dummy. */
        if (value != Py_None) {
                PyErr_SetString(PyExc_TypeError,
                  "instance exception may not have a separate value");
                goto raise_error;
        }
        else {
                /* Normalize to raise ,  */
                Py_DECREF(value);
                value = type;
                type = (PyObject*) ((PyInstanceObject*)type)->in_class;
                Py_INCREF(type);
        }
    }
    else if (PyType_IsSubtype(type->ob_type, (PyTypeObject*)PyExc_Exception)){
        /* Raising a new-style object (in Py2.5).
           The value should be a dummy. */
        if (value != Py_None) {
                PyErr_SetString(PyExc_TypeError,
                  "instance exception may not have a separate value");
                goto raise_error;
        }
        else {
                /* Normalize to raise ,  */
                Py_DECREF(value);
                value = type;
                type = type->ob_type;
                Py_INCREF(type);
        }
    }
    else {
        /* Not something you can raise.  You get an exception
           anyway, just not what you specified :-) */
        PyErr_Format(PyExc_TypeError,
                     "exceptions must be classes, instances, or "
                     "strings (deprecated), not %s",
                     type->ob_type->tp_name);
        goto raise_error;
    }
    PyErr_Restore(type, value, tb);
    return;
raise_error:
    Py_XDECREF(value);
    Py_XDECREF(type);
    Py_XDECREF(tb);
    return;
}

References

11th November 2007

Related material: