I'm wrapping some classes using Swig directors in Python.
Each of my class methods returns a MyError class. What happens is, once I derive a python class from one of my C++ and I forget to return the MyError() object but I return None of "pass" or I forget to return anything, my software crashes in the default constructor of the MyError class through a read access violation, and I'm not able to track this exception through a try/catch block.
What is the correct way in Swig to handle such kind of situations ?
Thanks!
Section 36.5.4 Exception unrolling in the SWIG documentation:
With directors routing method calls to Python, and proxies routing them to C++, the handling of exceptions is an important concern. By default, the directors ignore exceptions that occur during method calls that are resolved in Python. To handle such exceptions correctly, it is necessary to temporarily translate them into C++ exceptions. This can be done with the %feature("director:except") directive. The following code should suffice in most cases:
%feature("director:except") {
if ($error != NULL) {
throw Swig::DirectorMethodException();
}
}
This code will check the Python error state after each method call from a director into Python, and throw a C++ exception if an error occurred. This exception can be caught in C++ to implement an error handler. Currently no information about the Python error is stored in the Swig::DirectorMethodException object, but this will likely change in the future.
It may be the case that a method call originates in Python, travels up to C++ through a proxy class, and then back into Python via a director method. If an exception occurs in Python at this point, it would be nice for that exception to find its way back to the original caller. This can be done by combining a normal %exception directive with the director:except handler shown above. Here is an example of a suitable exception handler:
%exception {
try { $action }
catch (Swig::DirectorException &e) { SWIG_fail; }
}
The class Swig::DirectorException used in this example is actually a base class of Swig::DirectorMethodException, so it will trap this exception. Because the Python error state is still set when Swig::DirectorMethodException is thrown, Python will register the exception as soon as the C wrapper function returns.
Example
Here's an example test.i that illustrates the technique:
%module test
%module(directors="1") test
%feature("director");
%feature("director:except") {
if ($error != NULL) {
throw Swig::DirectorMethodException();
}
}
%exception {
try { $action }
catch (Swig::DirectorException &e) { SWIG_fail; }
}
%inline %{
class MyError {
int m_n;
public:
MyError(int n = 0) : m_n(n) {}
~MyError() {}
int get() const { return m_n; }
};
class Demo {
public:
Demo() {}
virtual ~Demo() {}
virtual MyError test() { return MyError(5); }
};
int func(Demo* d) { return d->test().get(); }
%}
After swigging and compiling, a demo:
>>> import test
>>> d=test.Demo() # default class implementation
>>> test.func(d) # Call virtual method in a C++ test function.
5
The above worked normally. Below overrides incorrectly:
>>> class Demo2(test.Demo): # New class
... def test(self): # Override virtual function
... return 7 # But don't return a MyError object.
...
>>> d=Demo2()
>>> test.func(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: SWIG director type mismatch in output value of type 'MyError'
That caught the exception and returned a useful exception. Below overrides correctly:
>>> class Demo2(test.Demo):
... def test(self):
... return test.MyError(7)
...
>>> d=Demo2()
>>> test.func(d)
7
Related
In the Python code below, the findFactory returns a FactoryBase instead of a FactoryBaseWrapper.
And I think this may be the cause of the problem.
Do you know how to tell python that FactoryBaseWrapper inherit from FactoryBase?
I tried "bases" in the class_ like this:
class_<FactoryBaseWrapper, bases<FactoryBase> boost::noncopyable>( "FactoryBase", no_init )
But It doesn't compile. Seems like I need to make the interface from FactoryBase too. But I can't, I need FactoryBaseWrapper because of a pure virtual function in Factorybase.
Thanks
Python Code:
_sourceFactory = _myFactoryMgr.findFactory(name)
foo = _sourceFactory.getOrCreateBase(myObj) # Problem Here
BoostPython Module:
class_<FactoryMgr, boost::noncopyable>("FactoryMgr",no_init)
.def("findFactory",&FactoryMgr::findFactory, return_value_policy<reference_existing_object>());
class_<FactoryBaseWrapper, boost::noncopyable>( "FactoryBase", no_init )
.def( "getOrCreateIndicator", &FactoryBaseWrapper::getOrCreateIndicator, return_value_policy<reference_existing_object>())
Cpp functions & Classes :
class FactoryMgr
{
FactoryBase* FactoryMgr::findFactory( const std::string& name );
}
class FactoryBaseWrapper :public FactoryBase, public wrapper<FactoryBase>
{
Base* FactoryBase::getOrCreateBase ( const CustomObject* myObj);
Base* createBase( string str )
{
return this->get_override( "createBase" )();
}
}
class FactoryBase
{
virtual Base* createBase( string str )=0;
...
}
I get this error:
Traceback (most recent call last):
File "XXX", line 149, in YYYY
_sourceFactory.getOrCreateBase(myObj)
Boost.Python.ArgumentError: Python argument types in
FactoryBase.getOrCreateBase(FactoryBase, CustomObject)
did not match C++ signature:
getOrCreateBase(FactoryBaseWrapper {lvalue}, Interface::CustomObject const*)
Here are examples of functions in my C API:
int do_something(struct object *with_this)
struct object *get_something(struct object *from_this)
do_something will return 0 for success or -1 and set errno for failure. get_something will return a valid pointer on success or NULL and set errno for failure.
I'm using to SWIG 2.0 to generate python bindings. For the do_something binding I'd like it to return an exception based on errno if it fails and return the Python None object if it succeeds. For the get_something binding I'd like it to return an exception based on errno if it fails and the opaque object if it succeeds.
The question is how do I get SWIG to do all of this magic for me?
Currently I'm using SWIG 2.0.
I can use %exception and define different exceptions per symbol, but I'd like it to be based on return type. I'll have a lot of the API functions and I don't want to list them out. So I can do this for each symbol:
%exception do_something {
$action
if (result < 0) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
%exception get_something {
$action
if (result == NULL) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
It would be a lot better if I could do something like this (like you can do with %typemap):
%exception int {
$action
if (result < 0) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
%exception struct object * {
$action
if (result == NULL) {
return PyErr_SetFromErrno(PyExc_RuntimeError);
}
}
Can I do something like this? If I had something like this, then that would take care of the exceptions. And I believe my get_something binding would be perfect.
My do_something binding would still need something like this to get it to return the Python None object if I use %typemap:
%typemap(out) int {
$result = Py_BuildValue("");
}
Does the magic I'm looking for exist or am I going about this all wrong? Is there a better way to do this?
To help answer this question I took your functions and created the following test.h file that included enough real code to actually illustrate the problem:
struct object { int bar; };
static int do_something(struct object *with_this) {
errno = EACCES;
return -1;
}
static struct object *get_something(struct object *from_this) {
errno = EAGAIN;
return NULL;
}
I'm fairly sure that %typemap(out) is the place to write this code in, not %exception, because the behaviour you want is determined by the return type, not the name of the function called. The fact that you also care about the value of the return further backs this up. You can to some degree avoid repetition using $typemap to reference other typemaps within yours.
So I wrote a little SWIG interface to illustrate this:
%module test
%{
#include <errno.h>
#include "test.h"
%}
%typemap(out) int do_something %{
if ($1 < 0) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$result = Py_None; // Return only controls exception
Py_INCREF($result);
%}
%typemap(out) struct object *get_something %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
// Return passed through unless NULL
$typemap(out,$1_ltype);
%}
%include "test.h"
Which worked when tested:
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.do_something(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: (13, 'Permission denied')
>>> test.get_something(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: (11, 'Resource temporarily unavailable')
>>>
This only worked though because I explicitly named do_something when matching the typemap, so the fallback without the named function is fine. If you just try to remove that restriction you'll get an error about recursive typemaps. You can work around that using %apply, e.g.
%typemap(out) struct object * MY_NULL_OR_ERRNO %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$typemap(out,$1_ltype);
%}
%apply struct object * MY_NULL_OR_ERRNO { struct object *get_something, struct object *some_other_function };
If that's too painful you can of course just write the typemap by hand:
%typemap(out) struct object * %{
if (!$1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
SWIG_fail;
}
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, $owner);
%}
Which seems the simplest option if you really want that everywhere a struct object* gets returned.
There are other possible solutions using %pythoncode or %pythonappend, but neither of them are really improvements over the above in my view.
I am creating my own Python extension (using SWIG, but I hope that is not relevant).
In the C++ side of it, I am using PyErr_NewException to create a custom exception object.
// C++ - create a custom Python Exception class.
m = Py_InitModule((char *) "MyModule", SwigMethods);
g_pyMyErr = PyErr_NewException( "MyModule.MyErr", 0, 0 );
Py_INCREF(g_pyMyErr);
int result = PyModule_AddObject(m, "MyErr", g_pyMyErr);
The above code returns success values and I can throw the above exception successfully and catch it in the Python client code.
The problem is this: When I refer to "MyErr" in Python code I get an error saying "MyErr" is not defined.
// Python client code - catch the exception
from MyModule import *
try:
causeException()
catch MyErr: # Error: MyErr is not defined.
pass
catch Exception:
pass
EDIT: My current thinking is that maybe SWIG is altering (mangling) the names of things.
You need to define the error then:
%{
class MyErr {};
%}
Also, I would suggest adding this, so that you could catch the exceptions as MyModule.MyErr and not as MyModule._MyModule.MyErr (since that is how they will be generated by SWIG):
%pythoncode %{
MyErr = _MyModule.MyErr
%}
I write a python wrapper for box2d, everything work perfect but a strange TypeError error occurs sometimes when calling method function exposed by boost python. It's a random behavior, not happened every time.
The problem python code:
try:
world = body.GetWorld() # world is b2World instance, body is b2Body instance
world.DestroyBody(body) # raise TypeError: 'NoneType' object is not callable
except TypeError: # catch it, and print some infomation
print "xxxxx", world # I got a b2World instance here
print "xxxxx", sys.getrefcount(world) # I got a value of 66 here
print "xxxxx", world.DestroyBody # I got a bound method object here
raise
It seems all okay. How does that happened?
And part of my wrapper code:
// [file]: https://github.com/layzerar/box2d-py/blob/master/python/world.cpp
// [project]: https://github.com/layzerar/box2d-py
template<class T>
inline void b2Func_ClearUserData(T& self)
{
using namespace boost::python;
xdecref((PyObject*)self.GetUserData());
self.SetUserData(NULL);
}
inline void b2World_DestroyBody(b2World& self, b2Body* body)
{
b2Assert(self.GetBodyCount() > 0);
b2Assert(self.IsLocked() == false);
b2Func_ClearUserData(*body);
self.DestroyBody(body);
}
class_<b2World, b2World_W , boost::noncopyable>("b2World")
//...
.def("CreateBody", b2World_CreateBody, return_internal_reference<>())
.def("DestroyBody", b2World_DestroyBody)
//...
;
Did I make an obvious mistake?
After a few days exploring I found the answer.
I make a mistake about the question, the exception is really not thrown by DestroyBody. It's thrown by a virtual function which is rewrite in python and called in DestroyBody function.
I'm getting errors like this:
2010-07-13 20:43:15.131
Python[1527:60f] main: Caught
OC_PythonException: :
LoginMenuSet instance has no attribute
'play_sound'
That's with this code:
#try {
[section loop]; //Loop through section
} #catch (NSException *exception) {
NSLog(#"Caught %#: %#", [exception name], [exception reason]);
}
I want the python exception to be printed normally with the traceback and everything else.
Thank you.
One trick to see Python exceptions is to call objc.setVerbose(1). This makes PyObjC slightly more verbose and causes it to print Python stack traces when converting exceptions from Python to Objective-C.
Here's my own solution:
In Objective-C class:
#try {
[section loop]; //Loop through section
} #catch (NSException *exception) {
NSLog(#"main: Caught %#: %#", [exception name], [exception reason]);
[self exception: [[exception userInfo] valueForKey: #"__pyobjc_exc_traceback__"]];
}
In python pyobjc subclass:
def exception_(self,trace):
traceback.print_tb(trace)
NSApplication.sharedApplication().terminate_(None) #Accept no errors
I, of-course, imported the traceback module.