构建Python绑定到C或C++库的最快方法是什么?
(如果这很重要,我正在使用Windows.)
ctypes是标准库的一部分,因此比swig更稳定和广泛可用,它总是给我带来问题.
使用ctypes,你需要满足任何编译时对python的依赖,你的绑定将适用于任何有ctypes的python,而不仅仅是它编译的那个.
假设您想在一个名为foo.cpp的文件中与一个简单的C++示例类进行对话:
#includeclass Foo{ public: void bar(){ std::cout << "Hello" << std::endl; } };
因为ctypes只能与C函数通信,所以你需要提供那些声明为extern"C"的函数.
extern "C" { Foo* Foo_new(){ return new Foo(); } void Foo_bar(Foo* foo){ foo->bar(); } }
接下来,您必须将其编译为共享库
g++ -c -fPIC foo.cpp -o foo.o g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
最后你必须编写你的python包装器(例如在fooWrapper.py中)
from ctypes import cdll lib = cdll.LoadLibrary('./libfoo.so') class Foo(object): def __init__(self): self.obj = lib.Foo_new() def bar(self): lib.Foo_bar(self.obj)
一旦你有了,你可以称之为
f = Foo() f.bar() #and you will see "Hello" on the screen
你应该看一下Boost.Python.以下是他们网站的简短介绍:
Boost Python Library是一个用于连接Python和C++的框架.它允许您快速无缝地将C++类函数和对象公开给Python,反之亦然,不使用特殊工具 - 只需使用C++编译器.它旨在非侵入性地包装C++接口,因此您不必更改C++代码以包装它,使Boost.Python成为将第三方库公开给Python的理想选择.该库使用高级元编程技术简化了用户的语法,因此包装代码具有一种声明性接口定义语言(IDL)的外观.
最快的方法是使用SWIG.
SWIG 教程中的示例:
/* File : example.c */ int fact(int n) { if (n <= 1) return 1; else return n*fact(n-1); }
接口文件:
/* example.i */ %module example %{ /* Put header files here or function declarations like below */ extern int fact(int n); %} extern int fact(int n);
在Unix上构建Python模块:
swig -python example.i gcc -fPIC -c example.c example_wrap.c -I/usr/local/include/python2.7 gcc -shared example.o example_wrap.o -o _example.so
用法:
>>> import example >>> example.fact(5) 120
请注意,您必须拥有python-dev.另外在某些系统中,python头文件将基于你安装它的方式在/usr/include/python2.7中.
从教程:
SWIG是一个相当完整的C++编译器,几乎支持所有语言功能.这包括预处理,指针,类,继承,甚至C++模板.SWIG还可用于将结构和类打包到目标语言的代理类中 - 以非常自然的方式公开底层功能.
我从这个页面开始我的Python < - > C++绑定之旅,目的是链接高级数据类型(多维STL向量和Python列表):-)
在尝试了基于ctypes和boost.python(而不是软件工程师)的解决方案后,我发现当需要高级数据类型绑定时它们很复杂,而我发现SWIG对于这种情况更加简单.
这个例子因此使用了SWIG,并且已经在Linux中进行了测试(但是SWIG可用并且在Windows中也被广泛使用).
目标是使Python可以使用C++函数,该函数采用2D STL向量形式的矩阵并返回每行的平均值(作为1D STL向量).
C++中的代码("code.cpp")如下:
#include#include "code.h" using namespace std; vector average (vector< vector > i_matrix) { // Compute average of each row.. vector averages; for (int r = 0; r < i_matrix.size(); r++){ double rsum = 0.0; double ncols= i_matrix[r].size(); for (int c = 0; c< i_matrix[r].size(); c++){ rsum += i_matrix[r][c]; } averages.push_back(rsum/ncols); } return averages; }
等效标题("code.h")是:
#ifndef _code #define _code #includestd::vector average (std::vector< std::vector > i_matrix); #endif
我们首先编译C++代码来创建一个目标文件:
g++ -c -fPIC code.cpp
然后,我们为C++函数定义SWIG接口定义文件("code.i").
%module code %{ #include "code.h" %} %include "std_vector.i" namespace std { /* On a side note, the names VecDouble and VecVecdouble can be changed, but the order of first the inner vector matters! */ %template(VecDouble) vector; %template(VecVecdouble) vector< vector >; } %include "code.h"
使用SWIG,我们从SWIG接口定义文件生成C++接口源代码.
swig -c++ -python code.i
我们最终编译生成的C++接口源文件并将所有内容链接在一起以生成可由Python直接导入的共享库("_"很重要):
g++ -c -fPIC code_wrap.cxx -I/usr/include/python2.7 -I/usr/lib/python2.7 g++ -shared -Wl,-soname,_code.so -o _code.so code.o code_wrap.o
我们现在可以在Python脚本中使用该函数:
#!/usr/bin/env python import code a= [[3,5,7],[8,10,12]] print a b = code.average(a) print "Assignment done" print a print b
查看pyrex或Cython.它们是类似Python的语言,用于连接C/C++和Python.
还有pybind11
,就像Boost.Python的轻量级版本,兼容所有现代C++编译器:
https://pybind11.readthedocs.io/en/latest/
本文声称Python完全是科学家需要的,基本上说:首先用Python编写原型.然后,当您需要加速部分时,使用SWIG并将此部分转换为C.
对于现代C++,请使用cppyy:http://cppyy.readthedocs.io/en/latest/
它基于Cling,Clang/LLVM的C++解释器.绑定是在运行时,不需要额外的中间语言.感谢Clang,它支持C++ 17.
使用pip安装它:
$ pip install cppyy
对于小项目,只需加载相关的库和您感兴趣的标题.例如,从ctypes示例获取代码是此线程,但在头部和代码部分中分开:
$ cat foo.h class Foo { public: void bar(); }; $ cat foo.cpp #include "foo.h" #includevoid Foo::bar() { std::cout << "Hello" << std::endl; }
编译它:
$ g++ -c -fPIC foo.cpp -o foo.o $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
并使用它:
$ python >>> import cppyy >>> cppyy.include("foo.h") >>> cppyy.load_library("foo") >>> from cppyy.gbl import Foo >>> f = Foo() >>> f.bar() Hello >>>
通过自动加载准备好的反射信息和cmake片段来支持大型项目来创建它们,以便已安装软件包的用户可以简单地运行:
$ python >>> import cppyy >>> f = cppyy.gbl.Foo() >>> f.bar() Hello >>>
借助LLVM,可以实现高级功能,例如自动模板实例化.继续这个例子:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]() >>> v.push_back(f) >>> len(v) 1 >>> v[0].bar() Hello >>>
注意:我是cppyy的作者.
我从未使用它,但我听说过关于ctypes的好东西.如果您正在尝试将其与C++一起使用,请务必避免通过名称修改extern "C"
.感谢您的评论,FlorianBösch.
我认为python的cffi可以是一个选项.
目标是从Python调用C代码.你应该能够在不学习第三语言的情况下这样做:每个选择都要求你学习他们自己的语言(Cython,SWIG)或API(ctypes).因此,我们尝试假设您了解Python和C,并最大限度地减少您需要学习的额外API.
http://cffi.readthedocs.org/en/release-0.7/
问题是,如果我理解正确的话,如何从Python调用C函数。然后最好的选择是Ctypes(BTW可在所有Python变体中移植)。
>>> from ctypes import * >>> libc = cdll.msvcrt >>> print libc.time(None) 1438069008 >>> printf = libc.printf >>> printf("Hello, %s\n", "World!") Hello, World! 14 >>> printf("%d bottles of beer\n", 42) 42 bottles of beer 19
有关详细指南,您可能需要参考我的博客文章。
其中一个官方Python文档包含有关使用C/C++扩展Python的详细信息.即使不使用SWIG,它也非常简单,在Windows上运行良好.
首先,您应该确定自己的特定目的。上面提到了有关扩展和嵌入Python解释器的官方Python文档,我可以添加一个很好的二进制扩展概述。用例可分为3类:
加速器模块:运行速度比CPython中运行的等效纯Python代码更快。
包装模块:将现有的C接口公开给Python代码。
低级系统访问:访问CPython运行时,操作系统或底层硬件的低级功能。
为了给其他感兴趣的人一个更广阔的视野,并且由于您的最初问题有点含糊(“对C或C ++库”),我认为此信息可能对您很有趣。在上面的链接上,您可以了解使用二进制扩展名及其替代方法的缺点。
除了建议的其他答案外,如果您需要加速器模块,还可以尝试Numba。它的工作原理是“通过在导入时,运行时或静态(使用附带的pycc工具)使用LLVM编译器基础结构生成优化的机器代码”。
除非您期望编写Java包装程序,否则Cython绝对是必经之路,在这种情况下,SWIG可能更可取。
我建议使用runcython
命令行实用程序,它使使用Cython的过程非常容易。如果您需要将结构化数据传递给C ++,请查看Google的protobuf库,它非常方便。
这是我使用这两种工具的最小示例:
https://github.com/nicodjimenez/python2cpp
希望它可以是一个有用的起点。