Search code examples
pythonc++boostboost-python

Pass C++ object instance to Python function


I have a queue of objects that I am dequeueing in order to get an object and process it in python before returning the result. I'm a bit unsure as to how it all fits together but from what I've gathered from various places I think I am pretty close.

I have a class that looks like this:

class PyData
{
public:

    PyData(
        const btVector3 &TORSO_LV,
        std::vector<std::tuple<float, float, float>> DsOsAVs,
        std::vector<btVector3> RF_FORCES,
        std::vector<btVector3> LF_FORCES,
        float slope,
        float compliance
        );

    std::tuple<float, float, float> m_TORSO_LV;
    std::vector<std::tuple<float, float, float>> m_DsOsAVS;
    std::vector<std::tuple<float, float, float>> m_RF_FORCES;
    std::vector<std::tuple<float, float, float>> m_LF_FORCES;

    float m_slope;
    float m_compliance;


    ~PyData();
};

and then I create a boost python module that looks like this:

BOOST_PYTHON_MODULE(pydata) {
    bp::class_<PyData>("PyData",
        bp::init<
            const btVector3,
            std::vector<std::tuple<float, float, float>>,
            std::vector<btVector3>,
            std::vector<btVector3>,
            float,
            float
        >())
        .def_readonly("Torso_LV", &PyData::m_TORSO_LV)
        .def_readonly("DsOsAVs", &PyData::m_DsOsAVS)
        .def_readonly("RF_FORCES", &PyData::m_RF_FORCES)
        .def_readonly("LF_FORCES", &PyData::m_LF_FORCES);
};

After every 33 ms I create a PyData object and place it into the queue. Something like this:

// Check the sample clock for sampling
    if (m_sampleClock.getTimeMilliseconds() > 33) {
        if (ContactManager::GetInstance().m_beingUsed) {
            PyData dat = BuildPyData();
            if (dat.m_compliance != 0.0f) {
                std::unique_lock <std::mutex> l(m_mutex);
                m_data.push_front(dat);
                m_NotEmptyCV.notify_one();
                l.unlock();
            }
        }

        m_sampleClock.reset();
    }

I then have a separate worker thread that dequeues the queue to obtain an object and send it off to a python function which looks like:

void ContactLearningApp::PythonWorkerThread() {

    printf("Start Python thread. \n");

    bp::object f = m_interface.attr("predict_on_data");

    while (true) {
        //printf("Inside while loop and waiting. \n");
        std::unique_lock<std::mutex> ul(m_mutex);
        while (m_data.size() <= 0) {
            m_NotEmptyCV.wait(ul);
        }
        PyData dat = m_data.back();
        m_data.pop_back();

        f(boost::python::ptr(&dat));

        ul.unlock();
        //m_ProcessedCV.notify_one();
        //bp::exec("print ('Hello from boost')", m_main_namespace);
    }

}

Basically, I am trying to pass in an object instantiated in c++ as a python argument but I am not sure how to piece it together. The python interpreter doesn't need a copy of the object so I am using boost::python::ptr. The python file is simple and I just want to print out the object received onto the console like this:

def predict_on_data(data):
    print("In Predict on Data")
    print(data)

I'm not sure how this integrates with the boost module. What would be the correct way to do this?


Solution

  • I've written some sample code based on your PyData data object; this code uses the boost::python data structures (tuple and list) for exchanging data to/from Python, as this is their intended use, but these can be populated by copying data into them from std::tuple and std::vector as needed.

    This works with Python 2.7 and boost 1.53. Hopefully you can use this to help; NB the call to initpydata() (generated function) is required after Py_Initialze().

    C++ code:

    #include <iostream>
    #include <vector>
    #include <tuple>
    #include <boost/python.hpp>
    #include <boost/python/list.hpp>
    
    class PyData
    {
        public:
    
        PyData() {}
    
        float m_slope;
        float m_compliance;
    
        boost::python::tuple    m_TORSO_LV;
        boost::python::list     m_DsOsAVS;
        boost::python::list     m_RF_FORCES;
        boost::python::list     m_LF_FORCES;
    
        void InitData()
        {
            // simulate setting up data
            m_slope = 1.0;
            m_compliance = 2.0;
    
            m_TORSO_LV = boost::python::make_tuple(3.0, 4.0, 5.0);
    
            m_DsOsAVS.append(boost::python::make_tuple(10.0, 11.0, 12.0));
            m_DsOsAVS.append(boost::python::make_tuple(20.0, 21.0, 22.0));
    
            // etc.
        }
    
        ~PyData() {}
    };
    
    BOOST_PYTHON_MODULE(pydata) {
    boost::python::class_<PyData>("PyData")
        .def_readwrite("Torso_LV", &PyData::m_TORSO_LV)
        .def_readwrite("DsOsAVs", &PyData::m_DsOsAVS)
        .def_readwrite("RF_FORCES", &PyData::m_RF_FORCES)
        .def_readwrite("LF_FORCES", &PyData::m_LF_FORCES)
        .def_readwrite("slope", &PyData::m_slope)
        .def_readwrite("compliance", &PyData::m_compliance)
        ;
    };
    
    int main (int argc, char * argv[])
    {
        Py_Initialize();
    
        initpydata();
    
        boost::python::object main=boost::python::import("__main__");
        boost::python::object global(main.attr("__dict__"));
        boost::python::object result = boost::python::exec_file("/home/andy/Python2.py", global, global);
        boost::python::object predict_on_data = global["predict_on_data"];
        if (!predict_on_data.is_none())
        {
            boost::shared_ptr<PyData> o(new PyData);
            o->InitData();
            predict_on_data(boost::python::ptr(o.get()));
            std::cout << "values in c++ object are now: " << o->m_slope << " and " << o->m_compliance << std::endl;
        }
    
        return 0;
    }
    

    Python code (Python2.py file in this example):

    def predict_on_data(o):
        print "In Python:"
        print repr(o)
        # print the data members in o
        print "o.slope is " + repr(o.slope)
        print "o.compliance is " + repr(o.compliance)
        print "o.Torso_LV is " + repr(o.Torso_LV)
        print "o.m_DsOsAVs is " + repr(o.DsOsAVs)
        # modify some data
        o.slope = -1.0
        o.compliance = -2.0
    

    Once running this should give output like this:

    In Python:
    <pydata.PyData object at 0x7f41200956e0>
    o.slope is 1.0
    o.compliance is 2.0
    o.Torso_LV is (3.0, 4.0, 5.0)
    o.m_DsOsAVs is [(10.0, 11.0, 12.0), (20.0, 21.0, 22.0)]
    values in c++ object are now: -1 and -2
    

    Hope this is useful.