使用pybind11 将C++代码编译为python模块

背景

我们大家平常使用的python实现都是cpython,所以使用C语言或者C++来写一些扩展的时候,就相当于在写cpython的插件。cpython的扩展关键在于要实现一个PyObject* PyInit_modulename(void)的函数,也叫initialization function,这个函数返回一个PyModuleDef 的instance。

本文中,gemfield将介绍如何使用pybind11 来实现cpython的扩展。

从源码安装pybind11环境

1,基于Ubuntu 16.04

2,安装必要的依赖

gemfield@ThinkPad-X1C:~$ apt update
gemfield@ThinkPad-X1C:~$ apt install vim git cmake python3-dev python3-pip
gemfield@ThinkPad-X1C:~$ pip3 install pytest

3,克隆pybind11仓库

gemfield@ThinkPad-X1C:~$ git clone https://github.com/pybind/pybind11

#在pybind11项目目录下
gemfield@ThinkPad-X1C:~/pybind11$ mkdir build && cd build && cmake ..
-- Building tests WITHOUT Eigen
-- Could NOT find Boost
-- Catch not detected. Interpreter tests will be skipped. Install Catch headers manually or use `cmake -DDOWNLOAD_CATCH=1` to fetch them automatically.
-- pybind11 v2.3.dev0
-- Configuring done
-- Generating done
-- Build files have been written to: /root/pybind11/build

4,编译

#编译
gemfield@ThinkPad-X1C:~/pybind11/build$ make check -j 4
......
===== 229 passed, 89 skipped in 5.67 seconds =====

#安装
gemfield@ThinkPad-X1C:~/pybind11/build$ make install

5,设置PYTHONPATH

#设置PYTHONPATH
export PYTHONPATH=$PYTHONPATH:/home/gemfield/pybind11

否则会报类似下面的错误:

/usr/bin/python3: No module named pybind11.__main__; ‘pybind11’ is a package and cannot be directly executed;

或者

/usr/bin/python3: No module named pybind11

绑定函数

1,源代码示例

//gemfield.cpp #include <pybind11/pybind11.h> 
int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(gemfield, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring     m.def("add", &add, "A function which adds two numbers");
}

2,编译

#该编译命令会生成 gemfield.cpython-35m-x86_64-linux-gnu.so
g++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` gemfield.cpp -o gemfield`python3-config --extension-suffix`

3,运行

......
Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gemfield
>>> gemfield.add(1,7)
8

4,发生了什么

上述PYBIND11_MODULE(gemfield, m)的宏展开了后,如下所示:

static void pybind11_init_gemfield(pybind11::module &); 
extern "C" __attribute__ ((visibility("default"))) PyObject *PyInit_gemfield() { 
    { 
        const char *compiled_ver ="3.5"; 
        const char *runtime_ver = Py_GetVersion(); 
        size_t len = std::strlen(compiled_ver); 
        if (std::strncmp(runtime_ver, compiled_ver, len) != 0 || (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) { 
            PyErr_Format(PyExc_ImportError, "Python version mismatch: module was compiled for Python %s, " "but the interpreter version is incompatible: %s.", compiled_ver, runtime_ver); 
            return nullptr; 
        } 
    } 
    auto m = pybind11::module("gemfield"); 
    try { 
        pybind11_init_gemfield(m); 
        return m.ptr(); 
    } catch (pybind11::error_already_set &e) { 
        PyErr_SetString(PyExc_ImportError, e.what()); 
        return nullptr; 
    } catch (const std::exception &e) { 
        PyErr_SetString(PyExc_ImportError, e.what()); 
        return nullptr; 
    } 
}

void pybind11_init_gemfield(pybind11::module &m){
    m.doc() = "pybind11 example plugin";
    m.def("add", &add, "A function which adds two numbers");
}

可以看到实现了PyObject *PyInit_gemfield()函数。

绑定类

既然是C++,就不能不提到class。

1,源代码示例

#文件syszuxav.cpp

#include <pybind11/pybind11.h>
#include <iostream>

extern "C" bool initav(const char* url);
class SYSZUXav {
    public:
        SYSZUXav(const std::string &url) : url(url){initav(url.c_str());}
        const std::string &getFrame() const;

    private:
        std::string url;
};

const std::string& SYSZUXav::getFrame() const
{
    std::cout<< url << std::endl;
    return url;
}

PYBIND11_MODULE(syszuxav, m) {
    pybind11::class_<SYSZUXav>(m, "SYSZUXav")
        .def(pybind11::init<const std::string &>())
        .def("getFrame", &SYSZUXav::getFrame);
}

2,编译

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

3,运行

不运行了,因为上面的initav符号还没有定义。

4,发生了什么

上述PYBIND11_MODULE(syszuxav, m)宏展开了后,代码如下所示:

extern "C" bool initav(const char* url);
class SYSZUXav {
    public:
        SYSZUXav(const std::string &url) : url(url){initav(url.c_str());}
        const std::string &getFrame() const;

    private:
        std::string url;
};

const std::string& SYSZUXav::getFrame() const
{
    std::cout<< url << std::endl;
    return url;
}

static void pybind11_init_syszuxav(pybind11::module &); 
extern "C" __attribute__ ((visibility("default"))) PyObject *PyInit_syszuxav() 
{ 
    { 
        const char *compiled_ver = "3.5"; 
        const char *runtime_ver = Py_GetVersion(); 
        size_t len = std::strlen(compiled_ver); 
        if (std::strncmp(runtime_ver, compiled_ver, len) != 0 || (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) { 
            PyErr_Format(PyExc_ImportError, "Python version mismatch: module was compiled for Python %s, \
                    but the interpreter version is incompatible: %s.", compiled_ver, runtime_ver); 
            return nullptr;
        } 
    } 
    auto m = pybind11::module("syszuxav"); 
    try { 
        pybind11_init_syszuxav(m); 
        return m.ptr(); 
    } catch (pybind11::error_already_set &e) { 
        PyErr_SetString(PyExc_ImportError, e.what()); 
        return nullptr; 
    } catch (const std::exception &e) { 
        PyErr_SetString(PyExc_ImportError, e.what()); 
        return nullptr; 
    } 
} 
void pybind11_init_syszuxav(pybind11::module &m)
{
    pybind11::class_<SYSZUXav>(m, "SYSZUXav").def(pybind11::init<const std::string &>()).def("getFrame", &SYSZUXav::getFrame);
}

可以看到实现了PyObject *PyInit_syszuxav()函数。

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