从Python调用C/C++?

Calling C/C++ from Python?

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

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


ctypes是标准库的一部分,因此比swig更稳定、更广泛,后者总是给我带来问题。

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

假设您有一个简单的C++示例类,您想在一个名为Foo.CPP的文件中进行对话:

1
2
3
4
5
6
7
8
#include <iostream>

class Foo{
    public:
        void bar(){
            std::cout <<"Hello" << std::endl;
        }
};

由于CTypes只能与C函数对话,因此需要提供声明它们为extern"C"的函数。

1
2
3
4
extern"C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}

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

1
2
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

最后,您必须编写Python包装器(例如在foowrapper.py中)

1
2
3
4
5
6
7
8
9
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)

一旦你拥有了它,你就可以这样称呼它

1
2
f = Foo()
f.bar() #and you will see"Hello" on the screen


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

The Boost Python Library is a framework for interfacing Python and
C++. It allows you to quickly and seamlessly expose C++ classes
functions and objects to Python, and vice-versa, using no special
tools -- just your C++ compiler. It is designed to wrap C++ interfaces
non-intrusively, so that you should not have to change the C++ code at
all in order to wrap it, making Boost.Python ideal for exposing
3rd-party libraries to Python. The library's use of advanced
metaprogramming techniques simplifies its syntax for users, so that
wrapping code takes on the look of a kind of declarative interface
definition language (IDL).


最快的方法是使用swig。

swig教程中的示例:

1
2
3
4
5
/* File : example.c */
int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

接口文件:

1
2
3
4
5
6
7
8
/* 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模块:

1
2
3
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

用途:

1
2
3
>>> import example
>>> example.fact(5)
120

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

从教程中:

SWIG is a fairly complete C++ compiler with support for nearly every language feature. This includes preprocessing, pointers, classes, inheritance, and even C++ templates. SWIG can also be used to package structures and classes into proxy classes in the target language — exposing the underlying functionality in a very natural manner.


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

在尝试了基于ctypes和boost.python(而不是软件工程师)的解决方案之后,我发现它们在需要高级别数据类型绑定时很复杂,而在这种情况下,我发现swig更简单。

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

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

C++中的代码("代码.CPP")如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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")是:

1
2
3
4
5
6
7
8
#ifndef _code
#define _code

#include <vector>

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

#endif

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

1
g++ -c -fPIC code.cpp

然后,我们为我们的C++函数定义了一个SWIG接口定义文件("代码I")。

1
2
3
4
5
6
7
8
9
10
11
12
13
%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++接口源代码。

1
swig -c++ -python code.i

我们最终编译生成的C++接口源文件,并将所有的东西链接在一起,生成一个可由Python直接输入的共享库("Y"):

1
2
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脚本中使用该函数:

1
2
3
4
5
6
7
8
9
#!/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


查看Pyrex或Cython。它们是介于C/C++和Python之间的Python语言。


EDOCX1也有1位,它就像Booj.python的轻量级版本,与所有现代C++编译器兼容:

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


本文声称Python是科学家所需要的一切,主要是说:首先用Python制作所有东西的原型。然后,当您需要加速一个部件时,使用swig并将此部件翻译为c。


我从来没有用过,但我听说过关于ctypes的好消息。如果您试图用C++来使用它,请确保通过EDCOX1的0个名称来规避名称的篡改。谢谢你的评论,弗洛里安B?SCH。


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

它基于CLAN,CLAN/LLVM的C++解释器。绑定在运行时,不需要额外的中间语言。多亏了Clang,它支持C++ 17。

使用PIP安装:

1
    $ pip install cppyy

对于小项目,只需加载相关的库和您感兴趣的头文件。例如,从ctypes中获取代码的例子就是这个线程,但是在头和代码部分中进行了拆分:

1
2
3
4
5
6
7
8
9
10
11
    $ cat foo.h
    class Foo {
    public:
        void bar();
    };

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

    void Foo::bar() { std::cout <<"Hello" << std::endl; }

编译:

1
2
    $ g++ -c -fPIC foo.cpp -o foo.o
    $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

并使用它:

1
2
3
4
5
6
7
8
9
    $ python
    >>> import cppyy
    >>> cppyy.include("foo.h")
    >>> cppyy.load_library("foo")
    >>> from cppyy.gbl import Foo
    >>> f = Foo()
    >>> f.bar()
    Hello
    >>>

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

1
2
3
4
5
6
    $ python
    >>> import cppyy
    >>> f = cppyy.gbl.Foo()
    >>> f.bar()
    Hello
    >>>

得益于LLVM,高级功能是可能的,例如自动模板实例化。要继续示例:

1
2
3
4
5
6
7
    >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
    >>> v.push_back(f)
    >>> len(v)
    1
    >>> v[0].bar()
    Hello
    >>>

注:我是CPPYY的作者。


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

The goal is to call C code from Python. You should be able to do so
without learning a 3rd language: every alternative requires you to
learn their own language (Cython, SWIG) or API (ctypes). So we tried
to assume that you know Python and C and minimize the extra bits of
API that you need to learn.

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


其中一个官方的Python文档包含了使用C/C++来扩展Python的细节。即使不使用swig,它也非常简单,在Windows上也能很好地工作。


Cython绝对是必经之路,除非您预期编写Java包装,在这种情况下SWIG可能是更好的。

我建议使用runcython命令行实用程序,这使得使用cython的过程非常简单。如果你需要把结构化的数据传递给C++,看看谷歌的ToBuFF库,非常方便。

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

https://github.com/nicodjimenez/python2cpp

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


如果我理解正确的话,问题是如何从Python调用C函数。最好的选择是ctypes(btw可移植到所有的python变体中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from ctypes import *
>>> libc = cdll.msvcrt
>>> print libc.time(None)
1438069008
>>> printf = libc.printf
>>> printf("Hello, %s
"
,"World!")
Hello, World!
14
>>> printf("%d bottles of beer
"
, 42)
42 bottles of beer
19

有关详细指南,请参阅我的博客文章。


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

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

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

除了建议的其他答案外,如果您想要一个加速器模块,您可以尝试使用numba。它的工作方式是"通过在导入时、运行时或静态(使用包含的pycc工具)使用llvm编译器基础结构生成优化的机器代码"。