我怎样才能在Python中实现一个C ++类,被C ++调用?
我有一个用C ++编写的类接口。 我有几个类来实现这个接口也用C ++编写。 这些被称为在一个更大的C + +程序的背景下,实质上实现“主”。 我希望能够用Python编写这个接口的实现,并允许它们在更大的C ++程序的上下文中使用,就像它们刚刚用C ++编写的一样。
关于python和c ++的接口已经有很多了,但是我不太清楚如何去做我想要的。 我可以find最近的是这里: 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 }
我希望这已经足够清楚了;)
这个答案有两个部分。 首先,您需要以Python的方式暴露您的接口,使得Python实现可以随意覆盖它的一部分。 然后你需要显示你的C ++程序( main
如何调用Python。
将现有的接口暴露给Python:
第一部分与SWIG很容易做到。 我稍微修改了您的示例场景以解决一些问题,并添加了一个用于testing的额外function:
// myif.h class myif { public: virtual float myfunc(float a) = 0; }; inline void runCode(myif *inst) { std::cout << inst->myfunc(5) << std::endl; }
现在我将看看这个问题,而不是将Pythonembedded到你的应用程序中,也就是说你在Python中启动excetion,而不是在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的导演function,并专门用于我们的界面。 其余的是标准的SWIG。
我写了一个testingPython实现:
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
确切地说,你希望从这个testing中看到什么。
将Pythonembedded到应用程序中:
接下来,我们需要实现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; }
基本上只是将Pythonembedded到另一个应用程序中 。 它的作品,并给出了你也希望看到的:
g ++ -Wall -Wextra -I / usr / include / python2.7 main.cc -o main -lpython2.7 。/主要 200.0 10 10
谜题的最后一部分是能够将您在Python中创build实例所获得的PyObject*
转换为myif *
。 SWIG再次使这个合理简单。
首先,我们需要让SWIG为我们公开一个头文件的运行时。 我们通过一个额外的电话给SWIG:
swig -Wall -c ++ -python -external-runtime runtime.h
接下来我们需要重新编译我们的SWIG模块,明确地给出SWIGtypes的表,知道一个名字,以便我们可以从我们的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,这样做:
。/主要 11
最小的例子; 请注意,由于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 ++函数)如何调用virtual foo()
,无论在c ++还是python中,都可以parsing为override。 你可以在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()) /* error */; Base& b=extractor();
PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();
引用http://wiki.python.org/moin/boost.python/Inheritance
“Boost.Python还允许我们表示C ++inheritance关系,这样包装的派生类就可以传递到需要值,指针或对基类的引用作为参数的地方。
有一些虚函数的例子可以解决第一部分(MyCl(myif))
对于这样做的具体例子, http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions
对于myif c = MyCl(); 你需要将你的python(模块)暴露给C ++。 这里有一些例子http://wiki.python.org/moin/boost.python/EmbeddingPython
基于Eudoxos的(非常有帮助)的回答,我已经拿了他的代码,并扩展它,现在有一个embedded式解释器,内置模块。
这个答案是Boost.Python相当于我的基于SWIG的答案 。
头文件myif.h:
class myif { public: virtual float myfunc(float a) const { return 0; } virtual ~myif() {} };
基本上就像问题一样,但是使用myfunc
的默认实现和虚拟析构函数。
对于Python的实现,MyCl.py与问题基本相同:
import myif class MyCl(myif.myif): def myfunc(self,a): return a*2.0
然后这留下了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(); } }
我在这里添加的关键部分,超越了“如何使用Boost.PythonembeddedPython?” 和“如何使用Boost.python扩展Python?” (Eudoxos回答)是“我怎样在同一个程序中同时做这两件事?”这个问题的答案。 解决方法是使用PyImport_AppendInittab
调用,该调用需要在模块加载时通常会调用的初始化函数,并将其注册为内置模块。 因此,当mycl.py说import myif
它最终导入内置的Boost.Python模块。
看一下Boost Python,这是在C ++和Python之间架设的最通用和最强大的工具。
没有真正的方法来直接使用Python来连接C ++代码。
SWIG确实处理了这个问题,但它构build了自己的包装器。
我更喜欢SWIG的另一种select是ctypes,但是为了使用它,你需要创build一个C包装器。
例如:
// myif.h class myif { public: virtual float myfunc(float a); };
像这样构build一个C封装器:
extern "C" __declspec(dllexport) float myif_myfunc(myif* m, float a) { return m->myfunc(a); }
由于您正在使用C ++构build,所以extern“C”允许C链接,因此您可以从dll轻松调用它,而__declspec(dllexport)允许从dll调用该函数。
在Python中:
from ctypes import * from os.path import dirname dlldir = dirname(__file__) # this strips it to the directory only dlldir.replace( '\\', '\\\\' ) # Replaces \ with \\ in dlldir lib = cdll.LoadLibrary(dlldir+'\\myif.dll') # Loads from the full path to your module. # Just an alias for the void pointer for your class c_myif = c_void_p # This tells Python how to interpret the return type and arguments lib.myif_myfunc.argtypes = [ c_myif, c_float ] lib.myif_myfunc.restype = c_float class MyCl(myif): def __init__: # Assume you wrapped a constructor for myif in C self.obj = lib.myif_newmyif(None) def myfunc(a): return lib.myif_myfunc(self.obj, a)
尽pipeSWIG为您做了这些工作,但是您可以根据自己的意愿修改任何东西,而不用担心在重新生成SWIG包装时必须重做的所有更改。
ctypes的一个问题是,它不处理STL结构,因为它是为C做的.SIGG会为你处理这个问题,但是你可以把它自己包装在C中。这取决于你。
这里是ctypes的Python文档:
http://docs.python.org/library/ctypes.html
另外,内置的dll应该和你的Python界面在同一个文件夹中(为什么它不是?)。
我很好奇,你为什么要从C ++中调用Python,而不是直接调用C ++实现呢?