Calling C/C++ from Python?
什么是最快的方法来构建一个Python绑定到C或C++库?
(如果这很重要,我会使用Windows。)
ctypes是标准库的一部分,因此比swig更稳定、更广泛,后者总是给我带来问题。
使用ctypes,您需要满足对python的任何编译时依赖性,并且您的绑定将在任何具有ctypes的python上工作,而不仅仅是针对它编译的。
假设您有一个简单的C++示例类,您想在一个名为Foo.CPP的文件中进行对话:
1 2 3 4 5 6 7 8 | #include <iostream> class Foo{ public: void bar(){ std::cout <<"Hello" << std::endl; } }; |
由于CTypes只能与C函数对话,因此需要提供声明它们为extern"C"的函数。
1 2 3 4 | extern"C" { Foo* Foo_new(){ return new Foo(); } void Foo_bar(Foo* foo){ foo->bar(); } } |
接下来,您必须将其编译到共享库中
1 2 | g++ -c -fPIC foo.cpp -o foo.o g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o |
最后,您必须编写Python包装器(例如在foowrapper.py中)
1 2 3 4 5 6 7 8 9 | 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) |
一旦你拥有了它,你就可以这样称呼它
1 2 | f = Foo() f.bar() #and you will see"Hello" on the screen |
你应该看看boost.python。以下是他们网站上的简短介绍:
The Boost Python Library is a framework for interfacing Python and
C++. It allows you to quickly and seamlessly expose C++ classes
functions and objects to Python, and vice-versa, using no special
tools -- just your C++ compiler. It is designed to wrap C++ interfaces
non-intrusively, so that you should not have to change the C++ code at
all in order to wrap it, making Boost.Python ideal for exposing
3rd-party libraries to Python. The library's use of advanced
metaprogramming techniques simplifies its syntax for users, so that
wrapping code takes on the look of a kind of declarative interface
definition language (IDL).
最快的方法是使用swig。
swig教程中的示例:
1 2 3 4 5 | /* File : example.c */ int fact(int n) { if (n <= 1) return 1; else return n*fact(n-1); } |
接口文件:
1 2 3 4 5 6 7 8 | /* 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模块:
1 2 3 | 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 |
用途:
1 2 3 | >>> import example >>> example.fact(5) 120 |
请注意,您必须具有python-dev。在某些系统中,python头文件将基于您安装它的方式位于/usr/include/python2.7中。
从教程中:
SWIG is a fairly complete C++ compiler with support for nearly every language feature. This includes preprocessing, pointers, classes, inheritance, and even C++ templates. SWIG can also be used to package structures and classes into proxy classes in the target language — exposing the underlying functionality in a very natural manner.
我在Python <-> C++绑定中从这个页面开始我的旅程,目的是链接高级数据类型(多维STL向量和Python列表):
在尝试了基于ctypes和boost.python(而不是软件工程师)的解决方案之后,我发现它们在需要高级别数据类型绑定时很复杂,而在这种情况下,我发现swig更简单。
因此,这个例子使用了swig,并且已经在Linux中进行了测试(但是swig是可用的,并且在Windows中也被广泛使用)。
目的是使一个C++函数可用于Python,该函数采用一个2D STL向量形式的矩阵,并返回每行的平均值(作为1D STL向量)。
C++中的代码("代码.CPP")如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <vector> #include"code.h" using namespace std; vector<double> average (vector< vector<double> > i_matrix) { // Compute average of each row.. vector <double> 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")是:
1 2 3 4 5 6 7 8 | #ifndef _code #define _code #include <vector> std::vector<double> average (std::vector< std::vector<double> > i_matrix); #endif |
我们首先编译C++代码来创建一个对象文件:
1 | g++ -c -fPIC code.cpp |
然后,我们为我们的C++函数定义了一个SWIG接口定义文件("代码I")。
1 2 3 4 5 6 7 8 9 10 11 12 13 | %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<double>; %template(VecVecdouble) vector< vector<double> >; } %include"code.h" |
使用SWIG,我们从SWIG接口定义文件生成C++接口源代码。
1 | swig -c++ -python code.i |
我们最终编译生成的C++接口源文件,并将所有的东西链接在一起,生成一个可由Python直接输入的共享库("Y"):
1 2 | 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脚本中使用该函数:
1 2 3 4 5 6 7 8 9 | #!/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。它们是介于C/C++和Python之间的Python语言。
EDOCX1也有1位,它就像Booj.python的轻量级版本,与所有现代C++编译器兼容:
https://pybind11.readthedocs.io/en/latest/最新/
本文声称Python是科学家所需要的一切,主要是说:首先用Python制作所有东西的原型。然后,当您需要加速一个部件时,使用swig并将此部件翻译为c。
我从来没有用过,但我听说过关于ctypes的好消息。如果您试图用C++来使用它,请确保通过EDCOX1的0个名称来规避名称的篡改。谢谢你的评论,弗洛里安B?SCH。
对于现代C++,使用CPPYY:http://cppyy.readthedocs.io/en/latest/最新/
它基于CLAN,CLAN/LLVM的C++解释器。绑定在运行时,不需要额外的中间语言。多亏了Clang,它支持C++ 17。
使用PIP安装:
1 | $ pip install cppyy |
对于小项目,只需加载相关的库和您感兴趣的头文件。例如,从ctypes中获取代码的例子就是这个线程,但是在头和代码部分中进行了拆分:
1 2 3 4 5 6 7 8 9 10 11 | $ cat foo.h class Foo { public: void bar(); }; $ cat foo.cpp #include"foo.h" #include <iostream> void Foo::bar() { std::cout <<"Hello" << std::endl; } |
编译:
1 2 | $ g++ -c -fPIC foo.cpp -o foo.o $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o |
并使用它:
1 2 3 4 5 6 7 8 9 | $ python >>> import cppyy >>> cppyy.include("foo.h") >>> cppyy.load_library("foo") >>> from cppyy.gbl import Foo >>> f = Foo() >>> f.bar() Hello >>> |
支持大型项目自动加载准备好的反射信息和创建这些反射信息的cmake片段,以便已安装包的用户可以简单地运行:
1 2 3 4 5 6 | $ python >>> import cppyy >>> f = cppyy.gbl.Foo() >>> f.bar() Hello >>> |
得益于LLVM,高级功能是可能的,例如自动模板实例化。要继续示例:
1 2 3 4 5 6 7 | >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]() >>> v.push_back(f) >>> len(v) 1 >>> v[0].bar() Hello >>> |
注:我是CPPYY的作者。
我认为针对python的cffi可以是一个选项。
The goal is to call C code from Python. You should be able to do so
without learning a 3rd language: every alternative requires you to
learn their own language (Cython, SWIG) or API (ctypes). So we tried
to assume that you know Python and C and minimize the extra bits of
API that you need to learn.
http://cffi.readthedocs.org/en/release-0.7/
其中一个官方的Python文档包含了使用C/C++来扩展Python的细节。即使不使用swig,它也非常简单,在Windows上也能很好地工作。
Cython绝对是必经之路,除非您预期编写Java包装,在这种情况下SWIG可能是更好的。
我建议使用
下面是我使用这两种工具所做的一个最小示例:
https://github.com/nicodjimenez/python2cpp
希望它能成为一个有用的起点。
如果我理解正确的话,问题是如何从Python调用C函数。最好的选择是ctypes(btw可移植到所有的python变体中)。
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> from ctypes import * >>> libc = cdll.msvcrt >>> print libc.time(None) 1438069008 >>> printf = libc.printf >>> printf("Hello, %s ","World!") Hello, World! 14 >>> printf("%d bottles of beer ", 42) 42 bottles of beer 19 |
有关详细指南,请参阅我的博客文章。
首先,你应该决定你的特殊目的是什么。上面提到了关于扩展和嵌入python解释器的官方python文档,我可以对二进制扩展进行很好的概述。用例可以分为三类:
- 加速器模块:比在cpython中运行的等效纯python代码更快。
- 包装模块:向Python代码公开现有的C接口。
- 低级系统访问:访问cpython运行时、操作系统或底层硬件的低级功能。
为了给其他感兴趣的人提供一个更广阔的视角,因为你最初的问题有点模糊("到C或C++库"),我认为这些信息可能对你很有意思。在上面的链接中,您可以看到使用二进制扩展及其替代方法的缺点。
除了建议的其他答案外,如果您想要一个加速器模块,您可以尝试使用numba。它的工作方式是"通过在导入时、运行时或静态(使用包含的pycc工具)使用llvm编译器基础结构生成优化的机器代码"。