从Python调用C / C ++?


520

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

(如果这很重要,我正在使用Windows。)

Answers:


169

您应该看看Boost.Python。以下是他们网站上的简短介绍:

Boost Python库是用于连接Python和C ++的框架。它使您可以快速而无缝地将C ++类的函数和对象暴露给Python,反之亦然,而无需使用特殊工具-仅使用C ++编译器即可。它旨在非侵入性地包装C ++接口,因此您不必为了包装而完全更改C ++代码,从而使Boost.Python成为将第三方库公开给Python的理想选择。该库对高级元编程技术的使用简化了用户的语法,因此包装代码具有一种声明性接口定义语言(IDL)的外观。


Boost.Python是Boost中更人性化的库之一,对于简单的函数调用API,它非常简单,并提供了您必须自己编写的样板。如果要公开面向对象的API,则要复杂一些。
jwfearn

15
Boost.Python是可想象的最糟糕的事情。对于每台新机器和每次升级,它都会出现链接问题。
米勒

14
在将近11年的时间里对这个答案的质量有所思考吗?
伊万斯

4
这仍然是连接python和c ++的最佳方法吗?
tushaR

8
也许您可以尝试pybind11,它比boost轻巧。
jdhao

658

ctypes模块是标准库的一部分,因此比swig更稳定和更易于使用,而swig总是会给我带来麻烦

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

假设您要在一个名为foo.cpp的文件中讨论一个简单的C ++示例类:

#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

14
这几乎是boost.python在单个函数调用中为您所做的。
马丁·贝克特

201
ctypes在python标准库中,而swig和boost没有。Swig和boost依赖于扩展模块,因此绑定到python次要版本,而独立共享对象则不是。建立一个痛饮或加强包装可能会很痛苦,ctypes没有建立要求。
FlorianBösch08年

25
boost依靠voodoo模板魔术和完全定制的构建系统,ctypes依赖于简单性。ctypes是动态的,boost是静态的。ctypes可以处理不同版本的库。提高不能。
FlorianBösch08年

32
在Windows上,我必须在函数签名中指定__declspec(dllexport)以便Python能够看到它们。从上面的示例可以看出: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald

13
别忘了随后通过提供一个Foo_delete函数并从python析构函数调用它或将对象包装在资源中来删除指针。
Adversus

57

最快的方法是使用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还可以用于以目标语言将结构和类打包为代理类,从而以非常自然的方式公开基础功能。


49

我从这一页的Python <-> C ++绑定开始了我的旅程,目的是链接高级数据类型(带有Python列表的多维STL向量):-)

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

因此,该示例使用了SWIG,并且已经在Linux中进行了测试(但是SWIG可用,并且在Windows中也被广泛使用)。

目的是使C ++函数可用于Python,该函数采用2D STL向量形式的矩阵并返回每一行的平均值(作为1D 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

一个真实的实现,其中在C ++代码中,stl向量作为非const引用传递,因此可以由python作为输出参数使用:lobianco.org/antonello/personal
Antonello


30

检查出pyrexCython。它们是类似于Python的语言,用于C / C ++和Python之间的接口。


1
Cython +1!我没有尝试过cffi,所以不能说哪个更好,但是我在Cython方面有很好的经验-您仍在编写Python代码,但可以在其中使用C。我很难用Cython设置构建过程,后来我在博客中解释了这一点:martinsosic.com/development/2016/02/08/…–
Martinsos

您可能需要改善答案,使其不再是仅链接的答案。
Adelin

我一直在使用Cython大约一个星期,我非常喜欢:1)我已经看到了ctypes的使用,它很丑陋,而且很容易出错,有很多陷阱2)它允许您使用一些Python代码并加快速度从单独的静态类型事物开始3)为C / C ++方法和对象编写Python包装器很简单4)仍然得到很好的支持。它可以为安装到venvs和交叉编译提供更多指导,这需要花费一些时间才能解决。这里有一个非常好的4小时视频教程:youtube.com/watch?
-

22

对于现代C ++,请使用cppyy: http

它基于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"
    #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的作者。


3
事实并非如此:Cython是一种类似于Python的编程语言,用于为Python编写C扩展模块(Cython代码与必要的C-API样板一起转换为C)。它提供了一些基本的C ++支持。使用cppyy进行编程仅涉及Python和C ++,没有语言扩展。它是完全运行时的,并且不会生成脱机代码(延迟生成的比例要好得多)。它以现代C ++为目标(包括自动模板实例化,移动,initializer_list,lambda等),并且本地支持PyPy(即不通过慢速C-API仿真层)。
Wim Lavrijsen

2
这份PyHPC'1​​6论文包含一系列基准数字。从那时起,尽管如此,CPython方面有了明显的改进。
Wim Lavrijsen

我喜欢这种方法,因为你没有做更多的集成工作与swigctypesboost.python。无需您编写代码来让python与c ++代码一起使用... python很难找出c ++。假设它确实有效。
Trevor Boyd Smith

cppyy非常有趣!我在文档中看到重新分发和预打包已得到处理。是否已知这也可以与打包python代码的工具(例如PyInstaller)配合使用?这与ROOT项目相关,还是可以利用其工作?
JimB

谢谢!我对PyInstaller并不熟悉,但是打包前向声明,路径和头文件的“字典”是编译为共享库的C ++代码。由于cppyy用于绑定C ++代码,因此我认为处理更多的C ++代码应该没问题。而且该代码不依赖于Python C-API(仅libcppyy模块),从而简化了事情。cppyy本身可以从conda-forge或pypi(pip)安装,因此,任何这些环境都可以使用。是的,cppyy最初是从PyROOT分支出来的,但此后又有了很大的改进,以至于ROOT团队在cppyy的基础上重新部署了PyROOT。
Wim Lavrijsen,


15

我从未使用过它,但是我听说过有关ctypes的好东西。如果您要在C ++中使用它,请确保通过来避开名称修饰extern "C"感谢弗洛里安·博斯(FlorianBösch)的评论。


13

我认为cffi for python是一个选择。

目的是从Python调用C代码。您应该能够在不学习第三语言的情况下进行操作:每种选择都要求您学习他们自己的语言(Cython,SWIG)或API(ctypes)。因此,我们尝试假设您了解Python和C,并尽量减少了您需要学习的API附加位。

http://cffi.readthedocs.org/en/release-0.7/


2
我认为这只能调用c(不能调用c ++),而仍然可以+1(我真的很喜欢cffi)。
安迪·海登

8

问题是,如果我理解正确的话,如何从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

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


可能值得注意的是,尽管ctypes是可移植的,但是您的代码需要Windows特定的C库。
Palec


6

除非您期望编写Java包装程序,否则Cython绝对是必经之路,在这种情况下,SWIG可能更可取。

我建议使用 runcython命令行实用程序,它使使用Cython的过程非常容易。如果您需要将结构化数据传递给C ++,请查看Google的protobuf库,它非常方便。

这是我使用这两种工具的最小示例:

https://github.com/nicodjimenez/python2cpp

希望它可以是一个有用的起点。


5

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

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

为了给其他感兴趣的人提供更广阔的视野,并且由于您的最初问题有点含糊(“对C或C ++库”),我认为此信息可能对您很有趣。在上面的链接上,您可以了解使用二进制扩展名及其替代方法的缺点。

除了建议的其他答案外,如果您需要加速器模块,还可以尝试Numba。它的工作原理是“通过在导入时间,运行时或静态(使用附带的pycc工具)使用LLVM编译器基础结构生成优化的机器代码”。


2

我喜欢cppyy,它很容易用C ++代码扩展Python,并在需要时大大提高了性能。

它功能强大且坦率地说非常易于使用,

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

import cppyy
cppyy.add_include_path("include")
cppyy.include('mylib/Buffer.h')


s = cppyy.gbl.buffer.Buffer()
numpy_array = np.empty(32000, np.float64)
s.get_numpy_array(numpy_array.data, numpy_array.size)

在C ++中:

struct Buffer {
  void get_numpy_array(int beam, double *ad, int size) {
    // fill the array
  }
}

您还可以非常轻松地(使用CMake)创建Python模块,这样您就可以避免一直重新编译C ++代码。


1

pybind11最小可运行示例

pybind11之前在https://stackoverflow.com/a/38542539/895245中提到过,但是我想在这里给出一个具体的用法示例以及有关实现的更多讨论。

总而言之,我强烈推荐pybind11,因为它确实很容易使用:您只需包含一个标头,然后pybind11使用模板魔术检查要公开给Python的C ++类并透明地进行。

这种模板魔术的缺点是,它会立即减慢编译速度,从而会给使用pybind11的任何文件增加几秒钟的时间,例如,请参见对此问题进行的调查PyTorch同意

这是一个最小的可运行示例,使您了解pybind11的出色程度:

class_test.cpp

#include <string>

#include <pybind11/pybind11.h>

struct ClassTest {
    ClassTest(const std::string &name) : name(name) { }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }
    std::string name;
};

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 &>())
        .def("setName", &ClassTest::setName)
        .def("getName", &ClassTest::getName)
        .def_readwrite("name", &ClassTest::name);
    return m.ptr();
}

class_test_main.py

#!/usr/bin/env python3

import class_test

my_class_test = class_test.ClassTest("abc");
print(my_class_test.getName())
my_class_test.setName("012")
print(my_class_test.getName())
assert(my_class_test.getName() == my_class_test.name)

编译并运行:

#!/usr/bin/env bash
set -eux
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

此示例说明pybind11如何使您轻松地将ClassTestC ++类公开给Python!编译会产生一个名为的文件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/895245Boost.Python所提到的相似,但因其摆脱了Boost项目中的膨胀而变得更加微不足道:

pybind11是一个轻量级的仅标头的库,它公开了Python中的C ++类型,反之亦然,主要是创建现有C ++代码的Python绑定。它的目标和语法类似于David Abrahams出色的Boost.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 ://docs.microsoft.com/zh-cn/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上进行了测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.