Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python method to boost function

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?

like image 666
Charles Avatar asked Feb 01 '10 19:02

Charles


People also ask

What is Boost Python?

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.

How to call a Python function?

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.

Does NumPy use boost?

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).


1 Answers

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();
like image 123
Charles Avatar answered Oct 27 '22 05:10

Charles