从Python调用C/ c++ ?

构造Python绑定到C或c++库的最快方法是什么?

(如果这很重要的话,我使用的是Windows。)

528512 次浏览

最快的方法是使用痛饮

swig# EYZ0的例子:

/* 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。以下是他们网站上的简短介绍:

Boost Python库是一个接口Python和Python的框架 c++。它允许您快速、无缝地公开c++类 函数和对象,反之亦然,不使用特殊的 工具——只是你的c++编译器。它被设计用来包装c++接口 非侵入性的,所以你不应该改变c++代码在 这一切都是为了包裹它,制作Boost。Python是曝光的理想选择 Python的第三方库。图书馆的先进使用 元编程技术为用户简化了语法,因此 包装代码呈现出一种声明性接口的外观 定义语言(IDL).

ctypes模块是标准库的一部分,因此比痛饮模块更稳定,也更广泛可用,而痛饮模块总是倾向于给我问题

使用ctypes,您需要满足python的任何编译时依赖,并且您的绑定将适用于任何具有ctypes的python,而不仅仅是它编译时针对的python。

假设你有一个简单的c++示例类,你想在一个名为foo.cpp的文件中进行对话:

#include <iostream>


class 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

我认为cffi可以作为python的一个选项。

目标是从Python调用C代码。你应该能够这样做 不学习第三种语言:每一种选择都需要你学习 学习他们自己的语言(Cython, SWIG)或API (ctypes)。所以我们尝试了 假设你懂Python和C语言,尽量减少多余的 你需要学习的API。

< a href = " http://cffi.readthedocs.org/en/release - 0.7 / " > http://cffi.readthedocs.org/en/release - 0.7 / < / >

我从这个页面开始了我的Python <-> c++绑定之旅,目标是链接高级数据类型(多维STL向量与Python列表):-)

在尝试了基于ctypesboost.python的解决方案后(并且不是软件工程师),我发现当需要高级数据类型绑定时,它们很复杂,而在这种情况下,我发现痛饮要简单得多。

因此,本例使用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

首先你应该确定你的特殊目的是什么。上面提到了关于扩展和嵌入Python解释器的Python官方文档,我可以添加一个很好的二进制扩展概述。用例可以分为3类:

  • 加速器模块:比在CPython中运行的等效纯Python代码运行得更快。
  • 包装器模块:将现有的C接口公开给Python代码。
  • 低级别系统访问:访问CPython运行时、操作系统或底层硬件的底层特性。

为了给其他感兴趣的人提供一些更广阔的视角,而且因为你最初的问题有点模糊(“到C或c++库”),我认为这些信息可能会让你感兴趣。在上面的链接中,您可以阅读到使用二进制扩展及其替代方案的缺点。

除了建议的其他答案,如果你想要一个加速器模块,你可以尝试Numba。它的工作原理是“在导入时、运行时或静态(使用包含的pycc工具)使用LLVM编译器基础设施生成优化的机器代码”。

问题是如何从Python中调用C函数,如果我理解正确的话。那么最好的选择是c类型(顺便说一句,可移植到所有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

要获得详细的指南,您可能需要参考我的博客文章

Cython绝对是最好的选择,除非您希望编写Java包装器,在这种情况下,SWIG可能更可取。

我建议使用runcython命令行实用工具,它使使用Cython的过程极其简单。如果你需要向c++传递结构化数据,可以看看谷歌的protobuf库,它非常方便。

以下是我使用这两种工具的一个最小示例:

https://github.com/nicodjimenez/python2cpp

希望这能成为一个有用的起点。

还有pybind11,它类似于提振。Python的轻量级版本,并且与所有现代c++编译器兼容:

< a href = " https://pybind11.readthedocs。io / en /最近/ noreferrer“rel = > https://pybind11.readthedocs.io/en/latest/ < / >

对于现代c++,使用cppyy: # EYZ0 < / p >

它基于Clang/LLVM的c++解释器kling。绑定是在运行时进行的,不需要额外的中间语言。得益于Clang,它支持c++ 17。

使用pip安装:

    $ pip install cppyy

对于小型项目,只需加载您感兴趣的相关库和标头。例如,从ctypes例子中获取的代码是这个线程,但是在头和代码部分中被分割:

    $ cat foo.h
class Foo {
public:
void bar();
};


$ cat foo.cpp
#include "foo.h"
#include <iostream>


void 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的作者。

我喜欢cppyy,它使得用c++代码扩展Python变得非常容易,在需要时极大地提高了性能。

它功能强大,坦白说使用起来非常简单,

这里是一个如何创建numpy数组并将其传递给c++中的类成员函数的示例。

cppyy_test.py

import cppyy
import numpy as np
cppyy.include('Buffer.h')




s = cppyy.gbl.Buffer()
numpy_array = np.empty(32000, np.float64)
s.get_numpy_array(numpy_array.data, numpy_array.size)
print(numpy_array[:20])

Buffer.h

struct Buffer {
void get_numpy_array(double *ad, int size) {
for( long i=0; i < size; i++)
ad[i]=i;
}
};

你也可以很容易地创建一个Python模块(使用CMake),这样你就可以避免一直重新编译c++代码。

Pybind11最小可运行示例

pybind11之前在https://stackoverflow.com/a/38542539/895245上提到过,但我想在这里给出一个具体的使用示例,并进一步讨论实现。

总而言之,我强烈推荐pybind11,因为它真的很容易使用:你只需要包含一个头文件,然后pybind11使用模板魔法来检查你想要公开给Python的c++类,并且是透明的。

这种模板魔法的缺点是,它会立即降低编译速度,为任何使用pybind11的文件增加几秒钟的时间,参见关于这个问题的调查示例。# EYZ1。已经在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++代码中理解到namestd::string,因此应该映射到Python的str对象。

编译生成一个名为class_test.cpython-36m-x86_64-linux-gnu.so的文件,class_test_main.py自动将其作为class_test本机定义模块的定义点。

也许只有当你试图用原生Python API手工做同样的事情时,你才会意识到这有多棒,比如这个例子,它有大约10倍多的代码:https://github.com/cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c在这个例子中,你可以看到C代码必须痛苦地显式地定义Python类,它包含的所有信息(成员、方法、进一步的元数据……)参见:

pybind11声称类似于https://stackoverflow.com/a/145436/895245中提到的Boost.Python,但更小,因为它从Boost项目内部的膨胀中解放出来:

pybind11是一个轻量级的仅头文件库,它在Python中公开c++类型,反之亦然,主要用于创建现有c++代码的Python绑定。它的目标和语法类似于出色的Boost。David Abrahams的Python库:通过使用编译时内省推断类型信息来最小化传统扩展模块中的样板代码。

Boost的主要问题。python——以及创建这样一个类似项目的原因——是Boost。Boost是一个非常庞大和复杂的实用程序库套件,几乎可以与现有的所有c++编译器一起使用。这种兼容性是有代价的:神秘的模板技巧和变通方法是必要的,以支持最古老和最容易出错的编译器样本。现在c++ 11兼容的编译器已经广泛可用,这个笨重的机器已经成为一个过度庞大和不必要的依赖。

可以把这个库看作一个小型的自包含版本的Boost。Python,去掉所有与绑定生成无关的东西。如果没有注释,核心头文件只需要大约4K行代码,并且依赖于Python(2.7或3。x,或PyPy2.7 >= 5.7)和c++标准库。这种紧凑的实现得益于c++ 11语言的一些新特性(特别是:元组、lambda函数和可变参数模板)。自创建以来,这个库已经超越了Boost。Python在很多方面都很出色,这使得在许多常见情况下的绑定代码大大简化。

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上测试。