Search code examples
pythonpython-2.7cpythonpython-internals

How to raise an exception in the init method for a CPython module


I am writing a CPython extension on top of a library that was written in C and I couldn't find a solution on how to raise an exception in the init method. So, I split it up and, basically, the constructor will save the attributes to the object and then you have to call an init method mr.initialize().

I find this method a bit ugly, and I want to find a solution to raise a ValueError exception in the constructor, here is my current code:

static int libzihc_MRLoader_init(libzihc_MRLoader *self, PyObject *args, PyObject *kwds) {
    double error_rate;
    const char *in;
    unsigned int capacity;

    static char *kwlist[] = {"in", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &in)) {
        return -1;
    }
    if (self->dataPath) {
        free(self->dataPath);
    }
    self->dataPath = malloc(sizeof(char)*(strlen(in)+1));
    if (self->dataPath) {
        strcpy(self->dataPath, in);
    } else {
        PyErr_NoMemory();
    }
    return 0;
}

And the added initialize method like so:

static PyObject* initialize(libzihc_MRLoader *self, PyObject *args, PyObject *kwds) {
    const char * msgError = NULL;
    int status = openRes(self->dataPath, &(self->ienaKey), &msgError);

    if(status != 0) {
        PyErr_SetString(PyExc_ValueError, msgError);
        printf("openRes returns %d\n",  status);
        return (PyObject *) NULL;
    }
    return Py_BuildValue("i", status);
}

From the CPython doc, if you want the interpreter to raise the exception you need to call one of the methods used to raise an exception in my case I use PyErr_SetString(PyExc_ValueError, msgError), and return null.

In this case the init method in CPython have to be static int, so I cannot return null, I did, however, remove the return statement, I saw the exception in stdout, but the interpreter did not stop.

How can I achieve this ?


Solution

  • You should return a negative value in libzihc_MRLoader_init, usually -1 for Python to catch it, search if an exception is set and end execution. At least, that's what type_call checks for after calling an objects __init__ method:

        type = obj->ob_type;
        if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&
            type->tp_init != NULL &&
            type->tp_init(obj, args, kwds) < 0) {  // if res < 0 returns NULL
            Py_DECREF(obj);
            obj = NULL;
        }
    

    So, in your specific case, you can move the code from initialize in libzihc_MRLoader_init and, if an error has occurred, return -1 instead of null to signal it.