Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm writing a SWIG wrapper around a custom C++ library which defines its own C++ exception types. The library's exception types are richer and more specific than standard exceptions. (For example, one class represents parse errors and has a collection of line numbers.) How do I propagate those exceptions back to Python while preserving the type of the exception?

like image 442
Barry Avatar asked Sep 08 '09 14:09

Barry


People also ask

How do you code exceptions in Python?

In Python, exceptions can be handled using a try statement. The critical operation which can raise an exception is placed inside the try clause. The code that handles the exceptions is written in the except clause. We can thus choose what operations to perform once we have caught the exception.

Are exceptions Pythonic?

An exception is a Python object that represents an error. When a Python script raises an exception, it must either handle the exception immediately otherwise it terminates and quits.

Which block allow you to handle the errors in Python?

The try block lets you test a block of code for errors. The except block lets you handle the error. The else block lets you execute code when there is no error.

What are swig files?

SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. SWIG is used with different types of target languages including common scripting languages such as Javascript, Perl, PHP, Python, Tcl and Ruby.


2 Answers

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 mylibrary try:     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, Extension  mylibrary_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.

like image 146
SeanManef Avatar answered Oct 08 '22 09:10

SeanManef


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.

like image 32
user1935043 Avatar answered Oct 08 '22 08:10

user1935043