How can I implement a C++ class in Python, to be called by C++? How can I implement a C++ class in Python, to be called by C++? python python

How can I implement a C++ class in Python, to be called by C++?


There's two parts to this answer. First you need to expose your interface in Python in a way which allows Python implementations to override parts of it at will. Then you need to show your C++ program (in main how to call Python.


Exposing the existing interface to Python:

The first part is pretty easy to do with SWIG. I modified your example scenario slightly to fix a few issues and added an extra function for testing:

// myif.hclass myif {   public:     virtual float myfunc(float a) = 0;};inline void runCode(myif *inst) {  std::cout << inst->myfunc(5) << std::endl;}

For now I'll look at the problem without embedding Python in your application, i.e. you start excetion in Python, not in int main() in C++. It's fairly straightforward to add that later though.

First up is getting cross-language polymorphism working:

%module(directors="1") module// We need to include myif.h in the SWIG generated C++ file%{#include <iostream>#include "myif.h"%}// Enable cross-language polymorphism in the SWIG wrapper. // It's pretty slow so not enable by default%feature("director") myif;// Tell swig to wrap everything in myif.h%include "myif.h"

To do that we've enabled SWIG's director feature globally and specifically for our interface. The rest of it is pretty standard SWIG though.

I wrote a test Python implementation:

import moduleclass MyCl(module.myif):  def __init__(self):    module.myif.__init__(self)  def myfunc(self,a):    return a*2.0cl = MyCl()print cl.myfunc(100.0)module.runCode(cl)

With that I was then able to compile and run this:

swig -python  -c++ -Wall myif.i g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7python mycl.py 200.010

Exactly what you'd hope to see from that test.


Embedding the Python in the application:

Next up we need to implement a real version of your mymain.cc. I've put together a sketch of what it might look like:

#include <iostream>#include "myif.h"#include <Python.h>int main(){  Py_Initialize();  const double input = 5.0;  PyObject *main = PyImport_AddModule("__main__");  PyObject *dict = PyModule_GetDict(main);  PySys_SetPath(".");  PyObject *module = PyImport_Import(PyString_FromString("mycl"));  PyModule_AddObject(main, "mycl", module);  PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict);  PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input));  PyObject *error = PyErr_Occurred();  if (error) {    std::cerr << "Error occured in PyRun_String" << std::endl;    PyErr_Print();  }  double ret = PyFloat_AsDouble(result);  std::cout << ret << std::endl;  Py_Finalize();  return 0;}

It's basically just standard embedding Python in another application. It works and gives exactly what you'd hope to see also:

g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7./main200.01010

The final piece of the puzzle is being able to convert the PyObject* that you get from creating the instance in Python into a myif *. SWIG again makes this reasonably straightforward.

First we need to ask SWIG to expose its runtime in a headerfile for us. We do this with an extra call to SWIG:

swig -Wall -c++ -python -external-runtime runtime.h

Next we need to re-compile our SWIG module, explicitly giving the table of types SWIG knows about a name so we can look it up from within our main.cc. We recompile the .so using:

g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

Then we add a helper function for converting the PyObject* to myif* in our main.cc:

#include "runtime.h"// runtime.h was generated by SWIG for us with the second call we mademyif *python2interface(PyObject *obj) {  void *argp1 = 0;  swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *");  const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0);  if (!SWIG_IsOK(res)) {    abort();  }  return reinterpret_cast<myif*>(argp1);}

Now this is in place we can use it from within main():

int main(){  Py_Initialize();  const double input = 5.5;  PySys_SetPath(".");  PyObject *module = PyImport_ImportModule("mycl");  PyObject *cls = PyObject_GetAttrString(module, "MyCl");  PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL);  myif *inst = python2interface(instance);  std::cout << inst->myfunc(input) << std::endl;  Py_XDECREF(instance);  Py_XDECREF(cls);  Py_Finalize();  return 0;}

Finally we have to compile main.cc with -DSWIG_TYPE_TABLE=myif and this gives:

./main11


Minimal example; note that it is complicated by the fact that Base is not pure virtual. There we go:

  1. baz.cpp:

    #include<string>#include<boost/python.hpp>using std::string;namespace py=boost::python;struct Base{  virtual string foo() const { return "Base.foo"; }  // fooBase is non-virtual, calling it from anywhere (c++ or python)  // will go through c++ dispatch  string fooBase() const { return foo(); }};struct BaseWrapper: Base, py::wrapper<Base>{  string foo() const{    // if Base were abstract (non-instantiable in python), then    // there would be only this->get_override("foo")() here    //    // if called on a class which overrides foo in python    if(this->get_override("foo")) return this->get_override("foo")();    // no override in python; happens if Base(Wrapper) is instantiated directly    else return Base::foo();  }};BOOST_PYTHON_MODULE(baz){  py::class_<BaseWrapper,boost::noncopyable>("Base")    .def("foo",&Base::foo)    .def("fooBase",&Base::fooBase)  ;}
  2. bar.py

    import syssys.path.append('.')import bazclass PyDerived(baz.Base):  def foo(self): return 'PyDerived.foo'base=baz.Base()der=PyDerived()print base.foo(), base.fooBase()print der.foo(), der.fooBase()
  3. Makefile

    default:       g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`

And the result is:

Base.foo Base.fooPyDerived.foo PyDerived.foo

where you can see how fooBase() (the non-virtual c++ function) calls virtual foo(), which resolves to the override regardless whether in c++ or python. You could derive a class from Base in c++ and it would work just the same.

EDIT (extracting c++ object):

PyObject* obj; // givenpy::object pyObj(obj); // wrap as boost::python object (cheap)py::extract<Base> ex(pyObj); if(ex.check()){ // types are compatible  Base& b=ex(); // get the wrapped object  // ...} else {  // error}// shorter, thrwos when conversion not possibleBase &b=py::extract<Base>(py::object(obj))();

Construct py::object from PyObject* and use py::extract to query whether the python object matches what you are trying to extract: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();


Quoting http://wiki.python.org/moin/boost.python/Inheritance

"Boost.Python also allows us to represent C++ inheritance relationships so that wrapped derived classes may be passed where values, pointers, or references to a base class are expected as arguments."

There are examples of virtual functions so that solves the first part (the one with class MyCl(myif))

For specific examples doing this, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

For the line myif c = MyCl(); you need to expose your python (module) to C++. There are examples here http://wiki.python.org/moin/boost.python/EmbeddingPython