如何在Python中实现一个C++类,以便被C++调用?
我有一个用C++写的类接口,还有一些用C++实现这个接口的类。这些类是在一个更大的C++程序中调用的,这个程序基本上就是“主程序”。我想用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);
};
我想要实现的功能大概是这样的:
// mycl.py
... some magic python stuff ...
class MyCl(myif):
def myfunc(a):
return a*2
然后,在我的C++代码中,我希望能这样写:
// mymain.cc
void main(...) {
... some magic c++ stuff ...
myif c = MyCl(); // get the python class
cout << c.myfunc(5) << endl; // should print 10
}
我希望这样说清楚了;)
6 个回答
引用自 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
这是一个简单的例子;要注意的是,因为 Base
不是纯虚拟的,所以事情变得有点复杂。我们开始吧:
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) ; }
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()
Makefile
default: g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
结果是:
Base.foo Base.foo
PyDerived.foo PyDerived.foo
在这里你可以看到 fooBase()
(这个是非虚拟的 C++ 函数)是如何调用虚拟的 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))();
从 PyObject*
构造 py::object
,然后使用 py::extract
来检查你想提取的 Python 对象是否匹配:PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* 错误 */; Base& b=extractor();
这个回答分为两个部分。首先,你需要在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;
}
现在我先不考虑在你的应用程序中嵌入Python,也就是说,你是在Python中启动异常,而不是在C++的int main()
中。稍后再添加这个功能也很简单。
首先,我们要实现跨语言多态性:
%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"
为此,我们在全局范围内和特定接口上启用了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)
有了这个,我就能够编译并运行这个:
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;
}
这基本上就是标准的在其他应用程序中嵌入Python。它可以正常工作,并且也给出了你希望看到的结果:
g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 ./main 200.0 10 10
最后一个难题是能够将你在Python中创建实例时得到的PyObject*
转换为myif *
。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*
转换为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);
}
现在这个功能已经到位,我们可以在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;
}
最后,我们必须使用-DSWIG_TYPE_TABLE=myif
来编译main.cc,这样就得到了:
./main 11