从Python调用C/C++?

sho*_*osh 493 c c++ python

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

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

Flo*_*sch 627

ctypes是标准库的一部分,因此比swig更稳定和广泛可用,它总是给我带来问题.

使用ctypes,你需要满足任何编译时对python的依赖,你的绑定将适用于任何有ctypes的python,而不仅仅是它编译的那个.

假设您想在一个名为foo.cpp的文件中与一个简单的C++示例类进行对话:

#include <iostream>

class Foo{
    public:
        void bar(){
            std::cout << "Hello" << std::endl;
        }
};
Run Code Online (Sandbox Code Playgroud)

因为ctypes只能与C函数通信,所以你需要提供那些声明为extern"C"的函数.

extern "C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}
Run Code Online (Sandbox Code Playgroud)

接下来,您必须将其编译为共享库

g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o
Run Code Online (Sandbox Code Playgroud)

最后你必须编写你的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)
Run Code Online (Sandbox Code Playgroud)

一旦你有了,你可以称之为

f = Foo()
f.bar() #and you will see "Hello" on the screen
Run Code Online (Sandbox Code Playgroud)

  • ctypes是在python标准库中,swig和boost不是.Swig和boost依赖于扩展模块,因此与python次要版本绑定,而独立的共享对象则不然.建立一个swig或boost包装可能是一种痛苦,ctypes没有构建要求. (191认同)
  • 在Windows上,我必须在我的函数签名中指定__declspec(dllexport)才能看到它们.从上面的例子中,这将对应于:`extern"C"{__ declspec(dllexport)Foo*Foo_new(){return new Foo(); } __declspec(dllexport)void Foo_bar(Foo*foo){foo-> bar(); } (31认同)
  • boost依赖于voodoo模板魔法和完全自定义的构建系统,ctypes依赖于简单性.ctypes是动态的,boost是静态的.ctypes可以处理不同版本的库.提升不了. (21认同)
  • 这几乎就是boost.python在单个函数调用中为您所做的. (14认同)
  • 不要忘记之后删除指针,例如提供一个`Foo_delete`函数并从python析构函数调用它或将对象包装在[resource]中(http://stackoverflow.com/questions/865115/how-do -i-正确-清理-A-蟒对象). (11认同)
  • +1太棒了,谢谢!也许这是常识,但是,对于后代,我使用osx并且不得不用`-install_name`替换`-soname`来编译. (8认同)
  • mgb:但是你让我感到愤怒,我鼓励你自己发一个答案.然而,为了显示提升优于ctypes的优势,它必须是相同的例子,少于4行C++包装器代码,少于2行构建指令和没有python线,哦,并且也适合它在一个屏幕上. (6认同)
  • 这个例子需要在函数指针上设置`restype`和`argtypes`。默认的“ restype”是“ c_int”,它会截断64位指针。使用`restype = c_void_p`时,指针值受到保护,但仍转换为Python整数。作为参数,除非已指定argtypes或将其手动重新包装在c_void_p中,否则它将再次默认为c_int。 (3认同)
  • 同意:我也有喘气和助力的麻烦.你应该在你的答案中明确哪些是文件,哪个名称应该得到它们. (2认同)
  • 查看答案中链接的 ctypes 参考。基本上,您必须手动将所有参数转换为 C 已知的基本类型,否则它们将被解释为 int。您可以手动定义自己的结构。不支持 python 字典中的变量类型,但当然,如果绝对需要,您可以使用带有 void 指针的 hack 。双参数示例:lib.Foo_baz(self.obj, ctypes.c_double(3.5)) (2认同)
  • @DustinBoswell:我扩展了这个答案的代码,包括带有double和integer参数以及整数返回值的函数 - 代码在pastebin(http://pastebin.com/pyUXf3RW)和(http://pastebin.com/0D700WPb)上.我不知道在哪里放置function.restype = c_double来返回一个double,而不是说如何传递一个STL vector <vector <double >>参数,这是我最终的需要.. (2认同)
  • 我已经完成了这里的所有操作,但仍然收到错误...`FileNotFoundError:找不到模块“path/to/libfoo.so”(或其依赖项之一)。尝试使用带有构造函数语法的完整路径。 (2认同)

Ral*_*lph 160

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

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

  • Boost.Python是可以想象的最糟糕的事情.对于每台新机器而言,每次升级都会遇到链接问题. (7认同)
  • 在将近11年的时间里对这个答案的质量有所思考吗? (6认同)
  • 也许您可以尝试[pybind11](https://github.com/pybind/pybind11),它比boost轻巧。 (4认同)
  • 这仍然是连接python和c ++的最佳方法吗? (2认同)

Ben*_*ein 53

最快的方法是使用SWIG.

SWIG 教程中的示例:

/* File : example.c */
int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}
Run Code Online (Sandbox Code Playgroud)

接口文件:

/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}

extern int fact(int n);
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

用法:

>>> import example
>>> example.fact(5)
120
Run Code Online (Sandbox Code Playgroud)

请注意,您必须拥有python-dev.另外在某些系统中,python头文件将基于你安装它的方式在/usr/include/python2.7中.

从教程:

SWIG是一个相当完整的C++编译器,几乎支持所有语言功能.这包括预处理,指针,类,继承,甚至C++模板.SWIG还可用于将结构和类打包到目标语言的代理类中 - 以非常自然的方式公开底层功能.


Ant*_*llo 48

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

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

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

目标是使Python可以使用C++函数,该函数采用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;
}
Run Code Online (Sandbox Code Playgroud)

等效标题("code.h")是:

#ifndef _code
#define _code

#include <vector>

std::vector<double> average (std::vector< std::vector<double> > i_matrix);

#endif
Run Code Online (Sandbox Code Playgroud)

我们首先编译C++代码来创建一个目标文件:

g++ -c -fPIC code.cpp
Run Code Online (Sandbox Code Playgroud)

然后,我们为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"
Run Code Online (Sandbox Code Playgroud)

使用SWIG,我们从SWIG接口定义文件生成C++接口源代码.

swig -c++ -python code.i
Run Code Online (Sandbox Code Playgroud)

我们最终编译生成的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
Run Code Online (Sandbox Code Playgroud)

我们现在可以在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
Run Code Online (Sandbox Code Playgroud)


Jas*_*ker 29

查看pyrexCython.它们是类似Python的语言,用于连接C/C++和Python.


Tom*_*ers 28

还有pybind11,就像Boost.Python的轻量级版本,兼容所有现代C++编译器:

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

  • **今天**!!*2020* 这应该是最佳答案!它是一个仅模板标头的库。许多大型相关项目都推荐它,例如`Pytorch` https://pytorch.org/tutorials/advanced/cpp_extension.html 也完全适用于`VS Community` Windows (9认同)
  • 还请提供一些示例,以使其比仅链接的答案更有用。 (2认同)
  • 我在以下位置展示了一个最小的可运行示例:/sf/answers/4226249331/ 来展示它是多么的棒。 (2认同)

Yuv*_*l F 19

本文声称Python完全是科学家需要的,基本上说:首先用Python编写原型.然后,当您需要加速部分时,使用SWIG并将此部分转换为C.


Wim*_*sen 17

对于现代C++,请使用cppyy:http://cppyy.readthedocs.io/en/latest/

它基于Cling,Clang/LLVM的C++解释器.绑定是在运行时,不需要额外的中间语言.感谢Clang,它支持C++ 17.

使用pip安装它:

    $ pip install cppyy
Run Code Online (Sandbox Code Playgroud)

对于小项目,只需加载相关的库和您感兴趣的标题.例如,从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; }
Run Code Online (Sandbox Code Playgroud)

编译它:

    $ g++ -c -fPIC foo.cpp -o foo.o
    $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o
Run Code Online (Sandbox Code Playgroud)

并使用它:

    $ python
    >>> import cppyy
    >>> cppyy.include("foo.h")
    >>> cppyy.load_library("foo")
    >>> from cppyy.gbl import Foo
    >>> f = Foo()
    >>> f.bar()
    Hello
    >>>
Run Code Online (Sandbox Code Playgroud)

通过自动加载准备好的反射信息和cmake片段来支持大型项目来创建它们,以便已安装软件包的用户可以简单地运行:

    $ python
    >>> import cppyy
    >>> f = cppyy.gbl.Foo()
    >>> f.bar()
    Hello
    >>>
Run Code Online (Sandbox Code Playgroud)

借助LLVM,可以实现高级功能,例如自动模板实例化.继续这个例子:

    >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
    >>> v.push_back(f)
    >>> len(v)
    1
    >>> v[0].bar()
    Hello
    >>>
Run Code Online (Sandbox Code Playgroud)

注意:我是cppyy的作者.

  • 事实并非如此:Cython是一种类似于Python的编程语言,用于为Python编写C扩展模块(Cython代码与必要的C-API样板一起转换为C)。它提供了一些基本的C ++支持。使用cppyy进行编程仅涉及Python和C ++,没有语言扩展。它是完全运行时的,并且不会生成脱机代码(延迟生成的比例要好得多)。它以现代C ++为目标(包括自动模板实例化,移动,initializer_lists,lambda等),并且本地支持PyPy(即不通过慢速C-API仿真层)。 (3认同)
  • [PyHPC'1​​6论文](https://dl.acm.org/citation.cfm?id=3019087)包含一系列基准数字。从那时起,尽管如此,CPython方面有了明显的改进。 (2认同)

Joh*_*ohn 15

我从未使用它,但我听说过关于ctypes的好东西.如果您正在尝试将其与C++一起使用,请务必避免通过名称修改extern "C".感谢您的评论,FlorianBösch.


mrg*_*oom 13

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

目标是从Python调用C代码.你应该能够在不学习第三语言的情况下这样做:每个选择都要求你学习他们自己的语言(Cython,SWIG)或API(ctypes).因此,我们尝试假设您了解Python和C,并最大限度地减少您需要学习的额外API.

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

  • 我认为这只能调用c(不是c ++),仍然是+1(我真的很喜欢cffi). (2认同)

Cir*_*四事件 10

pybind11 最小可运行示例

pybind11 之前在/sf/answers/2697977761/ 中提到过,但我想在这里给出一个具体的使用示例和一些关于实现的进一步讨论。

总而言之,我强烈推荐 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();
}
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

编译并运行:

#!/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
Run Code Online (Sandbox Code Playgroud)

标准输出:

abcz
2
012z
3
Run Code Online (Sandbox Code Playgroud)

如果我们尝试使用错误的类型,例如:

my_class_test.setI("abc")
Run Code Online (Sandbox Code Playgroud)

它按预期爆炸:

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'
Run Code Online (Sandbox Code Playgroud)

这个例子展示了 pybind11 如何让你毫不费力地将ClassTestC++ 类暴露给 Python!

值得注意的是,Pybind11 自动从 C++ 代码中理解namestd::string,因此应该映射到 Pythonstr对象。

编译生成一个名为的文件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 声称与/sf/answers/10180551/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,从而在许多常见情况下大大简化了绑定代码。

pybind11 也是当前 Microsoft Python C 绑定文档强调的唯一非本地替代方案:https ://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 上测试。


Jad*_*eda 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
Run Code Online (Sandbox Code Playgroud)

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


And*_*mbe 7

其中一个官方Python文档包含有关使用C/C++扩展Python的详细信息.即使不使用SWIG,它也非常简单,在Windows上运行良好.


小智 6

我喜欢 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])
Run Code Online (Sandbox Code Playgroud)

缓冲区.h

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

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


Yar*_*nko 5

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

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

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

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


nic*_*nez 5

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

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

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

https://github.com/nicodjimenez/python2cpp

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


归档时间:

查看次数:

388411 次

最近记录:

6 年,1 月 前