I have a method exported to Python using boost python that takes a boost::function as an argument.
From what I have read boost::python should support boost::function without much fuss, but when I try to call the function with a python method it gives me this error
Boost.Python.ArgumentError: Python argument types in
Class.createTimer(Class, int, method, bool)
did not match C++ signature:
createTimer(class Class {lvalue}, unsigned long interval,
class boost::function<bool _cdecl(void)> function, bool recurring=False)
I am calling it from python with this code
self.__class.createTimer( 3, test.timerFunc, False )
and in C++ it is defined as
boost::int32_t createTimer( boost::uint32_t interval, boost::function< bool() > function, bool recurring = false );
The goal here is a timer class where I can do something like
class->createTimer( 3, boost::bind( &funcWithArgs, arg1, arg2 ) )
to create a timer that executes the funcWithArgs. Thanks to boost bind this will work with pretty much any function or method.
So what is the syntax I need to use for boost::python to accept my python functions as a boost::function?
The Boost Python Library is a framework for interfacing Python and C++. It allows you to quickly and seamlessly expose C++ classes functions and objects to Python, and vice-versa, using no special tools -- just your C++ compiler.
Once we have defined a function, we can call it from another function, program, or even the Python prompt. To call a function we simply type the function name with appropriate parameters. >>> greet('Paul') Hello, Paul.
UPDATE: the library described in my original answer (https://github.com/ndarray/Boost.NumPy) has been integrated directly into Boost. Python as of Boost 1.63, and hence the standalone version is now deprecated. The text below now corresponds to the new, integrated version (only the namespace has changed).
Got an answer on the python mailing list, and after a bit of reworking and more research I got exactly what I wanted :)
I did see that post before mithrandi but I did not like the idea of having to declare the functions like that. With some fancy wrappers and a bit of python magic this can work and look good at the same time!
To start, wrap up your python object with code like this
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable( callable ) {}
bool operator()()
{
// These GIL calls make it thread safe, may or may not be needed depending on your use case
PyGILState_STATE gstate = PyGILState_Ensure();
bool ret = _callable();
PyGILState_Release( gstate );
return ret;
}
bp::object _callable;
};
boost::int32_t createTimerWrapper( Class* class, boost::uint64_t interval, bp::object function, bool recurring = false )
{
return class->createTimer( interval, boost::function<bool ()>( timer_func_wrapper_t( function ) ), recurring );
}
when in your class define the method like so
.def( "createTimer", &createTimerWrapper, ( bp::arg( "interval" ), bp::arg( "function" ), bp::arg( "recurring" ) = false ) )
With that little bit of wrapper you can work magic like this
import MyLib
import time
def callMePls():
print( "Hello world" )
return True
class = MyLib.Class()
class.createTimer( 3, callMePls )
time.sleep( 1 )
To mimic the C++ completely, we also need a boost::bind implementation which can be found here: http://code.activestate.com/recipes/440557/
With that, we can now do something like this
import MyLib
import time
def callMePls( str ):
print( "Hello", str )
return True
class = MyLib.Class()
class.createTimer( 3, bind( callMePls, "world" ) )
time.sleep( 1 )
EDIT:
I like to follow up on my questions when I can. I was using this code successfully for a while but I found out that this falls apart when you want to take boost::function's in object constructors. There is a way to make it work similarly to this but the new object you construct ends up with a different signature and will not work with other objects like itself.
This finally bugged me enough to do something about it and since I know more about boost::python now I came up with a pretty good 'fits all' solution using converters. This code here will convert a python callable to a boost::python< bool() > object, it can be easily modified to convert to other boost functions.
// Wrapper for timer function parameter
struct timer_func_wrapper_t
{
timer_func_wrapper_t( bp::object callable ) : _callable(callable) {}
bool operator()()
{
return _callable();
}
bp::object _callable;
};
struct BoostFunc_from_Python_Callable
{
BoostFunc_from_Python_Callable()
{
bp::converter::registry::push_back( &convertible, &construct, bp::type_id< boost::function< bool() > >() );
}
static void* convertible( PyObject* obj_ptr )
{
if( !PyCallable_Check( obj_ptr ) ) return 0;
return obj_ptr;
}
static void construct( PyObject* obj_ptr, bp::converter::rvalue_from_python_stage1_data* data )
{
bp::object callable( bp::handle<>( bp::borrowed( obj_ptr ) ) );
void* storage = ( ( bp::converter::rvalue_from_python_storage< boost::function< bool() > >* ) data )->storage.bytes;
new (storage)boost::function< bool() >( timer_func_wrapper_t( callable ) );
data->convertible = storage;
}
};
Then in your init code, ie, BOOST_PYTHON_MODULE(), just register the type by creating the struct
BOOST_PYTHON_MODULE(Foo)
{
// Register function converter
BoostFunc_from_Python_Callable();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With