Search code examples
pythonc++boostboost-python

Convert Python function to C++, via Boost Python, for use as a callback


In general what is the recommended way to pass a Python function through Boost-Python for use later in C++ code (i.e. as a callback in a C++ object)?

More specifically, I have a C++ class FooCPP that I've successfully exposed to Python via Boost-Python; the user interacts with the Python class Foo that runs the C++ counterpart under the hood. Contrived example:

# Foo.py

from foo_base import FooBase

class Foo(FooBase):
    ...

def callback(val=42.):
    return val

foo = Foo()
foo.run(callback)

And the Boost Python bindings:

// foo_bindings.cpp

#include "foo.hpp"
#include <boost/python.hpp>

namespace bp =  boost::python;

FooPython::Run(const bp::object& py_callback)
    // TODO: Do something with the python callback to make it a C++ function!
    std::function<double(double)> cpp_callback;
    FooCPP::Run(cpp_callback);
)

BOOST_PYTHON_MODULE(foo_base){
    bp::class_<FooPython>("FooBase")
    .def("run", &FooPython::Run)
    ;
}

So how can I address the TODO comment in foo_bindings.cpp?

I've gone through a number of related SO questions -- e.g. pass python function to boost c and sending py function as boost function arg -- and I'm familiar with the Boost-Python docs, but have not found a good solution/explanation. Thanks in advance!

Notes: C++11, boost v1.58.0, ubuntu 16.04

Update

I may have just found a solution, where I can implement a functor in foo_bindings.cpp, e.g.,

struct PythonCallback {
public:
    PythonCallback(bp::object cb_func) : cb_func_(cb_func) {}
    double operator() (const double& val) {
        // Call the callback function in python
        return cb_func_(val);
    }
private:
    bp::object cb_func_;
};

But then what should the FooCPP::Run signature be? I.e. what type is defined for the cpp_callback passed in?

And does the BOOST_PYTHON_MODULE code need to change for this callback functor?


Solution

  • Implement a functor in foo_bindings.cpp, where the callback is invoked with call:

    #include <boost/python.hpp>
    #include <boost/python/call.hpp>
    
    struct PythonCallback : {
    public:
        PythonCallback(PyObject* func) : cb_(func) {}
        double operator() (const double& value) {
            // Call the callback function in python
            return boost::python::call<double>(cb_, value);
        }
    private:
        PyObject* cb_;
    };