python的C与C++扩展编程(2)pybind11的函数转换

本文将介绍pybind11的一些经验

环境:ubuntu18.04 python3.7 anaconda

Pybind11

安装与简介

pybind11用于python的C++扩展,它与Cython的扩展有些许不同。实话说Cython说是用于C扩展,其实更像一个python的提速工具,它并不让你从头写一个C程序。而pybind11则不一样,它没有这些自动化操作,它是一个将C++程序转换为python可用的库的工具,所以你这次需要自己写一个CPP文件,然后使用pybind11将其编译为python的库。

为什么叫pybind11?我想是因为它支持C++11特性吧。除了可以将C++程序转为python的库以加速程序,我使用它的另一个原因则是我需要一些C++特有的库。使用pybind11就可以将C++和python结合起来,既能使用各种库,还能提速,最后还可以将程序逻辑部分侧重于python,提升易用易读性。现在很多大的python库都使用了pybind11做它们的C++扩展,如Caff2(已被吞并)等。

首先还是使用anaconda安装pybind11

conda install pybind11

还要一个C++的线代库Eigen3,后面要用:

conda install eigen

如果你没有c++环境记得还要装g++:

sudo apt-get install g++

如果你是Win环境那就是要安装VS或者用mingw,这里不详细教学了。

如果之后的程序编译有问题,大多都是include路径问题,g++用命令 -I 添加include路径一般能解决。

pybind11是一个只有头文件的库,conda的安装实际就是将头文件放在了conda/include里面,把这个路径添加进include就可以了。

简单使用

这次我们将从C++代码开始,文件名mylib.cpp:

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>

namespace py = pybind11;
typedef Eigen::MatrixXd Mat;
Mat myfunc(float z0a,float z0b,float scale,float s) {
    int N=static_cast<int> (s*scale);
    Mat p(N,N);
    std::complex<float> z0(z0a,z0b);
    std::complex<float> z;
    int color=0;
    for(size_t i = 0; i < N; i++)
    {
        for(size_t j = 0; j < N; j++)
        {
            z.real((i-0.5*N)/scale);
            z.imag((j-0.5*N)/scale);
            color=0;
            for(size_t k = 0; k < 200; k++)
            {
                if (std::abs(z)>4) {
                    color=k;
                    break;
                }
                else
                    z=z*z+z0;

            }
            p(i,j)=color;
        }
    }
return p;
}
PYBIND11_MODULE(mylib, m) {
    m.def("myf1", &myf1);
}

这个程序内容还是Juila Set的计算,不过变成C++版本的了,其中使用的Eigen库类似numpy,就是一个线代库,以也会经常用到它。

第一部分:

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>
namespace py = pybind11;
typedef Eigen::MatrixXd Mat;

我们首先include了pybind11,然后为了使用cout和eigen,我们还include了他们,但是你会发现这里不是正常的include,而是pybind11/eigen和pybind11/stl。这是因为pybind11需要对这些库进行些改动才能让你最后在python里面能够使用,比如能够cout然后在python里像print一样显示,让Eigen的数据类型能够被python接受,等等。

pybind11会自动处理比较常见的数据格式,这样你的C++函数的参数与返回值都不用特地处理,像这个程序它返回了Eigen的MatrixXd,到python那边并没有报错,还变成了numpy的矩阵,这个特性十分的方便。

然后就是一个没有什么特别的纯C++程序而已了:

Mat myfunc(float z0a,float z0b,float scale,float s) {
    int N=static_cast<int> (s*scale);
    Mat p(N,N);
    std::complex<float> z0(z0a,z0b);
    std::complex<float> z;
    int color=0;
    for(size_t i = 0; i < N; i++)
    {
        for(size_t j = 0; j < N; j++)
        {
            z.real((i-0.5*N)/scale);
            z.imag((j-0.5*N)/scale);
            color=0;
            for(size_t k = 0; k < 200; k++)
            {
                if (std::abs(z)>4) {
                    color=k;
                    break;
                }
                else
                    z=z*z+z0;

            }
            p(i,j)=color;
        }
    }

可以看到这就是个C++代码,你不用pybind11也可以运行它。

然后是重点:

PYBIND11_MODULE(mylib, m) {
    m.def("myf1", &myf1);
}

这是一个pybind11的宏,基本上写完C++程序后pybind11的处理配置全在这里了。这里展示的是如何将一个函数进行转换。重点是第一个参数要和文件名一样,这里就是mylib。
然后对于函数,就是用m.def进行定义,第一个参数是python使用时候的名称,第二个就是函数指针。

然后就可以开始编译了,我使用g++进行编译,就不动用CMAKE了。在终端输入以下命令:

g++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` mylib.cpp -o mylib`python3-config --extension-suffix`

如果说找不到库请用-I参数加上库文件的路径,一般不会有其他问题。

然后我们就获得了.so文件,这时就可以在python里import了:

from mylib import myf1

并且可以正常使用了:

z0a=-0.8
z0b=0.156
scale=100
p=myf1(z0a,z0b,scale,1)

这里我也测试了这个程序的性能,耗时0.025秒,比Cython慢很多,不知道是本来如此还是我写的代码菜了……

除了这样一个简单的函数转换,pybind对函数还有其他的设置,如:

设置函数说明:

m.def("myf1", &myf1, "A naive and simple function")

设置函数参数的名称和默认值,没错这个不输的话,你在C++里那些变量名python不认的:

m.def("myf1",&myf1,py::arg("z0a")=0,py::arg("z0b")=0,py::arg("scale"),py::arg("s"))

pybind11的函数扩展经验就分享到这。除了函数外,更大头的其实是类的扩展,pybind11对此有大量十分细节的转换与调控方法,我(可能)会在下一篇中进行分享。谢谢各位!

    原文作者:ykone
    原文地址: https://zhuanlan.zhihu.com/p/49737946
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞