从Python调用C / C ++?

时间:2008-09-28 05:34:20

标签: c++ python c

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

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

16 个答案:

答案 0 :(得分:586)

ctypes是标准库的一部分,因此比swig更稳定且广泛可用,problems总是倾向于给我{{3}}。

使用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

答案 1 :(得分:146)

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

  

Boost Python Library是一个用于连接Python和Python的框架   C ++。它允许您快速无缝地公开C ++类   Python的函数和对象,反之亦然,没有特殊之处   工具 - 只是你的C ++编译器。它旨在包装C ++接口   非侵入式,因此您不必更改C ++代码   所有这些都是为了包装它,使Boost.Python成为暴露的理想选择   第三方库到Python。图书馆的使用先进   元编程技术简化了用户的语法,因此   包装代码具有一种声明性接口的外观   定义语言(IDL)。

答案 2 :(得分:51)

最快的方法是使用SWIG

来自SWIG tutorial的示例:

/* 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还可用于将结构和类打包到目标语言的代理类中 - 以非常自然的方式公开底层功能。

答案 3 :(得分:45)

我在Python中开始了我的旅程&lt; - &gt;来自此页面的C ++绑定,目的是链接高级数据类型(多维STL向量与Python列表): - )

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

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

目标是使Python可以使用C ++函数,该函数采用2D STL向量形式的矩阵并返回每行的平均值(作为1D STL向量)。

C ++中的代码(&#34; code.cpp&#34;)如下:

#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;
}

等效标题(&#34; code.h&#34;)是:

#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(&#34; code.i&#34;)。

%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直接导入的共享库(&#34; _&#34;很重要):

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

答案 4 :(得分:27)

结帐pyrexCython。它们是类似Python的语言,用于连接C / C ++和Python。

答案 5 :(得分:20)

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

https://pybind11.readthedocs.io/en/latest/

答案 6 :(得分:18)

This paper, claiming Python to be all a scientist needs,基本上说:首先用Python编写原型。然后,当您需要加速部分时,使用SWIG并将此部分转换为C。

答案 7 :(得分:15)

我从未使用过它,但我听说过有关ctypes的好消息。如果您正在尝试将其与C ++一起使用,请务必通过extern "C"来避免名称损坏。 感谢您的评论,FlorianBösch。

答案 8 :(得分:14)

对于现代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"
    #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的作者。

答案 9 :(得分:11)

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

  

目标是从Python调用C代码。你应该能够这样做   没有学习第三语言:每个选择都需要你   学习他们自己的语言(Cython,SWIG)或API(ctypes)。所以我们试过了   假设您了解Python和C并最小化额外的位   您需要学习的API。

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

答案 10 :(得分:7)

其中一篇官方Python文档包含extending Python using C/C++的详细信息。 即使不使用SWIG,它也非常简单,在Windows上运行得非常好。

答案 11 :(得分:5)

首先,你应该决定你的特殊目的是什么。上面提到了关于extending and embedding the Python interpreter的官方Python文档,我可以添加一个好的overview of binary extensions。用例可分为3类:

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

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

除了建议的其他答案外,如果您需要加速器模块,可以尝试Numba。通过在导入时,运行时或静态(使用包含的pycc工具)&#34;使用LLVM编译器基础结构生成优化的机器代码,它可以正常工作。

答案 12 :(得分:5)

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

有关详细指南,您可以参考my blog article

答案 13 :(得分:5)

Cython肯定是要走的路,除非你期望编写Java包装器,在这种情况下,SWIG可能更好。

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

以下是我使用这两种工具制作的最小例子:

https://github.com/nicodjimenez/python2cpp

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

答案 14 :(得分:5)

pybind11最小可运行示例

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

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

此模板魔术的缺点是,它会立即减慢编译速度,从而会给使用pybind11的任何文件增加几秒钟的时间,例如参见the investigation done on this issuePyTorch agrees。提出了解决此问题的建议:https://github.com/pybind/pybind11/pull/2445

这里是一个最小的可运行示例,让您了解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如何使您轻松地将ClassTest C ++类公开给Python!编译会产生一个名为class_test.cpython-36m-x86_64-linux-gnu.so的文件,class_test_main.py会自动将其作为class_test本机定义模块的定义点。

也许,只有当您尝试使用本机Python API手动执行相同操作时,才会意识到它的强大程度,例如,请参见以下示例,该示例具有大约10倍的代码:{{3} }在该示例中,您可以看到C代码如何痛苦地,明确地定义Python类及其包含的所有信息(成员,方法,其他元数据...)。另请参阅:

pybind11声称与Inheritance in Python C++ extension中提到的Boost.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,从而在许多常见情况下大大简化了绑定代码。

在当前的Microsoft Python C绑定文档中,

pybind11也是唯一的非本地替代方法,网址为:https://stackoverflow.com/a/145436/895245https://docs.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上进行了测试。

答案 15 :(得分:1)

我喜欢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 ++代码。