如何在Python中实现C++类,由C++调用?

hal*_*al3 37 c++ python swig boost-python

我有一个用C++编写的类接口.我有一些实现此接口的类也是用C++编写的.这些是在更大的C++程序的上下文中调用的,它基本上实现了"main".我希望能够在Python中编写这个接口的实现,并允许它们在更大的C++程序的上下文中使用,就好像它们只是用C++编写的一样.

有很多关于连接python和C++的文章,但我无法弄清楚如何做我想要的.我能找到的最近的是:http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions,但这不是没错.

更具体地说,假设我有一个现有的C++接口定义如下:

// myif.h
class myif {
   public:
     virtual float myfunc(float a);
};
Run Code Online (Sandbox Code Playgroud)

我想要做的是:

// mycl.py
... some magic python stuff ...
class MyCl(myif):
  def myfunc(a):
    return a*2
Run Code Online (Sandbox Code Playgroud)

然后,回到我的C++代码中,我希望能够说出类似的话:

// mymain.cc
void main(...) {
  ... some magic c++ stuff ...
  myif c = MyCl();  // get the python class
  cout << c.myfunc(5) << endl;  // should print 10
}
Run Code Online (Sandbox Code Playgroud)

我希望这很清楚;)

Fle*_*exo 39

这个答案分为两个部分.首先,您需要以允许Python实现随意覆盖部分内容的方式在Python中公开您的接口.然后你需要展示你的C++程序(main如何调用Python.


将现有接口公开给Python:

第一部分很容易用SWIG做.我稍微修改了您的示例场景以修复一些问题并添加了额外的测试功能:

// myif.h
class myif {
   public:
     virtual float myfunc(float a) = 0;
};

inline void runCode(myif *inst) {
  std::cout << inst->myfunc(5) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

现在我将在没有在您的应用程序中嵌入Python的情况下查看问题,即您在Python中开始排除,而不是int main()在C++中开始.稍后添加它是相当简单的.

首先是让跨语言多态性工作:

%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"
Run Code Online (Sandbox Code Playgroud)

为此,我们在全球范围内启用了SWIG的导演功能,特别是我们的界面.其余部分是非常标准的SWIG.

我写了一个测试Python实现:

import module

class MyCl(module.myif):
  def __init__(self):
    module.myif.__init__(self)
  def myfunc(self,a):
    return a*2.0

cl = MyCl()

print cl.myfunc(100.0)

module.runCode(cl)
Run Code Online (Sandbox Code Playgroud)

然后我就可以编译并运行它:

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

python mycl.py 
200.0
10

你希望从那个测试中看到什么.


在应用程序中嵌入Python:

接下来我们需要实现mymain.cc的真实版本.我已经把它的外观描绘成一幅草图:

#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;
}
Run Code Online (Sandbox Code Playgroud)

它基本上只是在另一个应用程序中嵌入Python的标准.它的工作原理也完全符合您的希望:

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

最后一个难题是能够PyObject*将从Python中创建实例所获得的内容转换为myif *.SWIG再次使这一点变得相当简单.

首先,我们需要让SWIG在头文件中为我们公开它的运行时.我们通过额外拨打SWIG来做到这一点:

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

接下来我们需要重新编译我们的SWIG模块,明确给出SWIG知道名称的类型表,以便我们可以从main.cc中查找它.我们重新编译.so使用:

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

然后我们添加一个辅助函数来转换main.cc中的PyObject*to myif*:

#include "runtime.h"
// runtime.h was generated by SWIG for us with the second call we made

myif *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);
}
Run Code Online (Sandbox Code Playgroud)

现在这已经到位,我们可以从内部使用它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;
}
Run Code Online (Sandbox Code Playgroud)

最后我们必须编译main.cc,-DSWIG_TYPE_TABLE=myif这给出了:

./main
11


eud*_*xos 12

最小的例子; 请注意,它Base不是纯粹的虚拟事实.我们去:

  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)
      ;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. bar.py

    import sys
    sys.path.append('.')
    import baz
    
    class PyDerived(baz.Base):
      def foo(self): return 'PyDerived.foo'
    
    base=baz.Base()
    der=PyDerived()
    print base.foo(), base.fooBase()
    print der.foo(), der.fooBase()
    
    Run Code Online (Sandbox Code Playgroud)
  3. Makefile文件

    default:
           g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
    
    Run Code Online (Sandbox Code Playgroud)

结果是:

Base.foo Base.foo
PyDerived.foo PyDerived.foo
Run Code Online (Sandbox Code Playgroud)

在那里你可以看到fooBase()(非虚拟c ++函数)如何调用virtual foo(),无论是在c ++还是python中,它都会解析为覆盖.您可以在C++中从Base派生一个类,它的工作方式也是一样的.

编辑(提取c ++对象):

PyObject* obj; // given
py::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 possible
Base &b=py::extract<Base>(py::object(obj))();
Run Code Online (Sandbox Code Playgroud)

构造py::objectfrom PyObject*并用于py::extract查询python对象是否与您要提取的内容相匹配:PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();


Joh*_*erg 10

引用http://wiki.python.org/moin/boost.python/Inheritance

"Boost.Python还允许我们表示C++继承关系,以便可以传递包装的派生类,其中值,指针或对基类的引用被期望作为参数."

有一些虚函数的例子,以便解决第一部分(具有类MyCl(myif)的部分)

对于这样做的具体示例,http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

对于线myif c = MyCl(); 你需要将你的python(模块)暴露给C++.这里有一些例子http://wiki.python.org/moin/boost.python/EmbeddingPython


Fle*_*exo 8

根据Eudoxos的(非常有用的)答案,我已经采用了他的代码并扩展了它,现在有一个嵌入式解释器,带有内置模块.

这个答案是Boost.Python相当于我基于SWIG的答案.

头文件myif.h:

class myif {
public:
  virtual float myfunc(float a) const { return 0; }
  virtual ~myif() {}
};
Run Code Online (Sandbox Code Playgroud)

基本上就像问题一样,但是默认的实现myfunc和虚拟析构函数.

对于Python实现,MyCl.py我和问题基本相同:

import myif

class MyCl(myif.myif):
  def myfunc(self,a): 
    return a*2.0
Run Code Online (Sandbox Code Playgroud)

然后离开mymain.cc,其中大部分是基于Eudoxos的答案:

#include <boost/python.hpp>
#include <iostream>
#include "myif.h"

using namespace boost::python;

// This is basically Eudoxos's answer:
struct MyIfWrapper: myif, wrapper<myif>{
  float myfunc(float a) const {
    if(this->get_override("myfunc")) 
      return this->get_override("myfunc")(a);
    else 
      return myif::myfunc(a);
  }
};

BOOST_PYTHON_MODULE(myif){
  class_<MyIfWrapper,boost::noncopyable>("myif")
    .def("myfunc",&myif::myfunc)
  ;
}
// End answer by Eudoxos

int main( int argc, char ** argv ) {
  try {
    // Tell python that "myif" is a built-in module
    PyImport_AppendInittab("myif", initmyif);
    // Set up embedded Python interpreter:
    Py_Initialize();

    object main_module = import("__main__");
    object main_namespace = main_module.attr("__dict__");

    PySys_SetPath(".");
    main_namespace["mycl"] = import("mycl");

    // Create the Python object with an eval()
    object obj = eval("mycl.MyCl()", main_namespace);

    // Find the base C++ type for the Python object (from Eudoxos)
    const myif &b=extract<myif>(obj)();
    std::cout << b.myfunc(5) << std::endl;

  } catch( error_already_set ) {
    PyErr_Print();
  }
}
Run Code Online (Sandbox Code Playgroud)

我在这里添加的关键部分,超出了"我如何使用Boost.Python嵌入Python?" 和"如何使用Boost.python扩展Python?" (这是由Eudoxos回答的)是"如何在同一个程序中同时进行这两个问题?"这个问题的答案.对此的解决方案在于PyImport_AppendInittab调用,该调用采用初始化函数,该函数通常在加载模块时调用并将其注册为内置模块.因此,当mycl.py说它import myif最终导入内置的Boost.Python模块时.

  • 这个答案中的源代码是完整的,但是为了方便起见,我把整个这个例子放在http://static.lislan.org.uk/~ajw/boostpyvirt.tar.gz上. (2认同)