构造Python绑定到C或c++库的最快方法是什么?
(如果这很重要的话,我使用的是Windows。)
构造Python绑定到C或c++库的最快方法是什么?
(如果这很重要的话,我使用的是Windows。)
当前回答
我从本页开始了我的Python <-> c++绑定之旅,目标是链接高级数据类型(多维STL向量与Python列表):-)
已经尝试了基于ctypes和boost的解决方案。当需要高级数据类型绑定时,我发现它们很复杂,而在这种情况下,我发现SWIG要简单得多。
因此,本例使用SWIG,并且已经在Linux中进行了测试(但SWIG是可用的,并且在Windows中也广泛使用)。
目标是为Python提供一个c++函数,该函数接受二维STL向量形式的矩阵,并返回每一行的平均值(作为一维STL向量)。
c++中的代码("code.cpp")如下:
#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")是:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > 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<double>;
%template(VecVecdouble) vector< vector<double> >;
}
%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
其他回答
Pybind11最小可运行示例
pybind11之前在https://stackoverflow.com/a/38542539/895245上提到过,但我想在这里给出一个具体的使用示例,并进一步讨论实现。
总而言之,我强烈推荐pybind11,因为它真的很容易使用:你只需要包含一个头文件,然后pybind11使用模板魔法来检查你想要公开给Python的c++类,并且是透明的。
这个模板魔法的缺点是,它会立即降低编译速度,为任何使用pybind11的文件增加几秒钟的时间,参见关于这个问题的调查。PyTorch表示同意。解决这一问题的建议已在https://github.com/pybind/pybind11/pull/2445上提出
下面是一个最小的可运行示例,让你感受一下pybind11有多棒:
class_test.cpp
#include <string>
#include <pybind11/pybind11.h>
struct ClassTest {
ClassTest(const std::string &name, int i) : name(name), i(i) { }
void setName(const std::string &name_) { name = name_; }
const std::string getName() const { return name + "z"; }
void setI(const int i) { this->i = i; }
const int getI() const { return i + 1; }
std::string name;
int i;
};
namespace py = pybind11;
PYBIND11_PLUGIN(class_test) {
py::module m("my_module", "pybind11 example plugin");
py::class_<ClassTest>(m, "ClassTest")
.def(py::init<const std::string &, int>())
.def("setName", &ClassTest::setName)
.def("getName", &ClassTest::getName)
.def_readwrite("name", &ClassTest::name)
.def("setI", &ClassTest::setI)
.def("getI", &ClassTest::getI)
.def_readwrite("i", &ClassTest::i);
return m.ptr();
}
class_test_main.py
#!/usr/bin/env python3
import class_test
my_class_test = class_test.ClassTest("abc", 1);
print(my_class_test.getName())
print(my_class_test.getI())
my_class_test.setName("012")
my_class_test.setI(2)
print(my_class_test.getName())
print(my_class_test.getI())
assert(my_class_test.getName() == "012z")
assert(my_class_test.getI() == 3)
编译并运行:
#!/usr/bin/env bash
set -eux
sudo apt install pybind11-dev
g++ `python3-config --cflags` -shared -std=c++11 -fPIC class_test.cpp \
-o class_test`python3-config --extension-suffix` `python3-config --libs`
./class_test_main.py
Stdout输出:
abcz
2
012z
3
如果我们试图使用错误的类型,例如:
my_class_test.setI("abc")
它像预期的那样爆炸了:
Traceback (most recent call last):
File "/home/ciro/test/./class_test_main.py", line 9, in <module>
my_class_test.setI("abc")
TypeError: setI(): incompatible function arguments. The following argument types are supported:
1. (self: my_module.ClassTest, arg0: int) -> None
Invoked with: <my_module.ClassTest object at 0x7f2980254fb0>, 'abc'
这个例子展示了pybind11如何允许您毫不费力地将ClassTest c++类暴露给Python!
值得注意的是,Pybind11自动从c++代码中理解name是std::string,因此应该映射到Python str对象。
编译生成一个名为class_test.cpython-36m-x86_64-linux-gnu的文件。因此,class_test_main.py自动选择哪个class_test_main.py作为class_test原生定义模块的定义点。
Perhaps the realization of how awesome this is only sinks in if you try to do the same thing by hand with the native Python API, see for example this example of doing that, which has about 10x more code: https://github.com/cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c On that example you can see how the C code has to painfully and explicitly define the Python class bit by bit with all the information it contains (members, methods, further metadata...). See also:
python- c++扩展可以获得一个c++对象并调用其成员函数吗? 将c++类实例暴露给python嵌入式解释器 一个完整的和最小的例子类(不是方法)与Python C扩展? 在c++中嵌入Python,并使用Boost从c++代码中调用方法。Python Python c++扩展中的继承
pybind11声称类似于Boost。在https://stackoverflow.com/a/145436/895245上提到了Python,但更简单,因为它从Boost项目内部的膨胀中解放出来:
pybind11 is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code. Its goals and syntax are similar to the excellent Boost.Python library by David Abrahams: to minimize boilerplate code in traditional extension modules by inferring type information using compile-time introspection. The main issue with Boost.Python—and the reason for creating such a similar project—is Boost. Boost is an enormously large and complex suite of utility libraries that works with almost every C++ compiler in existence. This compatibility has its cost: arcane template tricks and workarounds are necessary to support the oldest and buggiest of compiler specimens. Now that C++11-compatible compilers are widely available, this heavy machinery has become an excessively large and unnecessary dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~4K lines of code and depend on Python (2.7 or 3.x, or PyPy2.7 >= 5.7) and the C++ standard library. This compact implementation was possible thanks to some of the new C++11 language features (specifically: tuples, lambda functions and variadic templates). Since its creation, this library has grown beyond Boost.Python in many ways, leading to dramatically simpler binding code in many common situations.
pybind11也是当前Microsoft Python C绑定文档中唯一强调的非原生替代:https://learn.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2019(存档)。
在Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0上测试。
最快的方法是使用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还可以用于用目标语言将结构和类打包为代理类——以非常自然的方式公开底层功能。
Cython绝对是最好的选择,除非您希望编写Java包装器,在这种情况下,SWIG可能更可取。
我建议使用runcython命令行实用工具,它使使用Cython的过程极其简单。如果你需要向c++传递结构化数据,可以看看谷歌的protobuf库,它非常方便。
以下是我使用这两种工具的一个最小示例:
https://github.com/nicodjimenez/python2cpp
希望这能成为一个有用的起点。
我认为cffi可以作为python的一个选项。
目标是从Python调用C代码。你应该能够这样做 不学习第三种语言:每一种选择都需要你学习 学习他们自己的语言(Cython, SWIG)或API (ctypes)。所以我们尝试了 假设你懂Python和C语言,尽量减少多余的 你需要学习的API。
http://cffi.readthedocs.org/en/release-0.7/
首先你应该确定你的特殊目的是什么。上面提到了关于扩展和嵌入Python解释器的官方Python文档,我可以添加一个很好的二进制扩展概述。用例可以分为3类:
加速器模块:比在CPython中运行的等效纯Python代码运行得更快。 包装器模块:将现有的C接口公开给Python代码。 低级系统访问:访问CPython运行时、操作系统或底层硬件的低级特性。
为了给其他感兴趣的人提供一些更广阔的视角,而且因为你最初的问题有点模糊(“到C或c++库”),我认为这些信息可能会让你感兴趣。在上面的链接中,您可以阅读到使用二进制扩展及其替代方案的缺点。
除了建议的其他答案外,如果您想要一个加速模块,您可以尝试Numba。它的工作原理是“在导入时、运行时或静态(使用包含的pycc工具)使用LLVM编译器基础设施生成优化的机器代码”。