How do I propagate C++ exceptions to Python in a SWIG wrapper library? How do I propagate C++ exceptions to Python in a SWIG wrapper library? python python

How do I propagate C++ exceptions to Python in a SWIG wrapper library?


I know this question is a few weeks old but I just found it as I was researching a solution for myself. So I'll take a stab at an answer, but I'll warn in advance it may not be an attractive solution since swig interface files can be more complicated than hand coding the wrapper. Also, as far as I can tell, the swig documentation never deals directly with user defined exceptions.

Let's say you want to throw the following exception from your c++ code module, mylibrary.cpp, along with a little error message to be caught in your python code:

throw MyException("Highly irregular condition...");  /* C++ code */

MyException is a user exception defined elsewhere.

Before you begin, make a note of how this exception should be caught in your python. For our purposes here, let's say you have python code such as the following:

import mylibrarytry:    s = mylibrary.do_something('foobar')except mylibrary.MyException, e:    print(e)

I think this describes your problem.

My solution involves making four additions to your swig interface file (mylibrary.i) as follows:

Step 1:In the header directive (the usually unnamed %{...%} block) add a declaration for the pointer to the python aware exception, which we will call pMyException. Step 2 below will define this:

%{#define SWIG_FILE_WITH_INIT  /* for eg */extern char* do_something(char*);   /* or #include "mylibrary.h" etc  */static PyObject* pMyException;  /* add this! */%}

Step 2:Add an initialization directive (the "m" is particularly egregious, but that's what swig v1.3.40 currently needs at that point in its constructed wrapper file) - pMyException was declared in step 1 above:

%init %{    pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);    Py_INCREF(pMyException);    PyModule_AddObject(m, "MyException", pMyException);%}

Step 3:As mentioned in an earlier post, we need an exception directive - note "%except(python)" is deprecated. This will wrap the c+++ function "do_something" in a try-except block which catches the c++ exception and converts it to the python exception defined in step 2 above:

%exception do_something {    try {        $action    } catch (MyException &e) {        PyErr_SetString(pMyException, const_cast<char*>(e.what()));        SWIG_fail;    }}/* The usual functions to be wrapped are listed here: */extern char* do_something(char*);

Step 4:Because swig sets up a python wrapping (a 'shadow' module) around the .pyd dll, we also need to make sure our python code can 'see through' to the .pyd file. The following worked for me and it's preferable to editing the swig py wrapper code directly:

%pythoncode %{    MyException = _mylibrary.MyException%}

It's probably too late to be of much use to the original poster, but perhaps somebody else will find the suggestions above of some use. For small jobs you may prefer the cleanness of a hand-coded c++ extension wrapper to the confusion of a swig interface file.


Added:

In my interface file listing above, I omitted the standard module directive because I only wanted to describe the additions necessary to make exceptions work. But your module line should look like:

%module mylibrary

Also, your setup.py (if you are using distutils, which I recommend at least to get started) should have code similar to the following, otherwise step 4 will fail when _mylibrary is not recognized:

/* setup.py: */from distutils.core import setup, Extensionmylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'],                    sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],)setup(name="mylibrary",        version="1.0",        description='Testing user defined exceptions...',        ext_modules=[mylibrary_module],        py_modules = ["mylibrary"],)

Note the compile flag /EHsc, which I needed on Windows to compile exceptions. Your platform may not require that flag; google has the details.

I have tested the code using my own names which I have converted here to "mylibrary" and "MyException" to help in generalizing the solution. Hopefully the transcription errors are few or none. The main points are the %init and %pythoncode directives along with

static PyObject* pMyException;

in the header directive.

Hope that clarifies the solution.


I'll add a bit here, since the example given here now says that "%except(python)" is deprecated...

You can now (as of swig 1.3.40, anyhow) do totally generic, script-language-independent translation. My example would be:

%exception {     try {        $action    } catch (myException &e) {        std::string s("myModule error: "), s2(e.what());        s = s + s2;        SWIG_exception(SWIG_RuntimeError, s.c_str());    } catch (myOtherException &e) {        std::string s("otherModule error: "), s2(e.what());        s = s + s2;        SWIG_exception(SWIG_RuntimeError, s.c_str());    } catch (...) {        SWIG_exception(SWIG_RuntimeError, "unknown exception");    }}

This will generate a RuntimeError exception in any supported scripting language,including Python, without getting python specific stuff in your other headers.

You need to put this before the calls that want this exception handling.


From the swig documentation

%except(python) {try {$function}catch (RangeError) {    PyErr_SetString(PyExc_IndexError,"index out-of-bounds");    return NULL;}}