Python方便但效率低,C++效率高但不方便,那不如把他们合起来用?
本篇介绍Python作为API、C++作为底层实现的方式之一:pybind11.

想要联合编译Python和C++有很多种方法和工具,我目前在用的是pybind11,给我感觉挺方便好用的。

安装pybind11

对于Linux来说,如果官方源里面有的话,可以直接从官方源安装,如:

1
$ sudo pacman -S pybind11

否则可以根据官方文档安装,但强烈不建议PyPI里的包(功能不完全)!

再退一步,不装也没关系,直接把它源码从Github仓库扒下来,后面会讲到要怎么用:

1
$ git clone https://github.com/pybind/pybind11.git

联合编译

这种方式相对传统(但常用),利用的是Python的打包工具setuptools(一般默认装了),可以利用pip进行管理。

示例目录如下:

1
2
3
4
5
6
.
├── pyproject.toml
├── setup.py
└── src
├── bind.cpp
└── main.cpp
  • main.cpp是实现代码;
    1
    int add(int i, int j) { return i + j; }
  • bind.cpp是接口函数,记录需要编译哪些代码;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <pybind11/pybind11.h>
    #include "main.cpp"

    namespace py = pybind11;

    // example是项目名,即“import example“
    PYBIND11_MODULE(example, m) {
    // def是项目内的函数声明:
    // 参数一是函数名,即“example.add(i, j)”
    // 参数二是对应C++函数的指针
    // 参数三是函数说明,即docstring
    m.def("add", &add, "A function that adds two numbers");
    }
  • setup.pypyproject.toml是setuptools的安装配置文件;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from setuptools import setup
    from pybind11.setup_helpers import Pybind11Extension

    # module
    ext_modules = [
    Pybind11Extension(
    "example", # 包名
    ["src/bind.cpp"] # 接口文件
    ),
    ]

    setup(
    name="example", # 包名(和上面一样)
    ext_modules=ext_modules,
    python_requires=">=3.6"
    )
    1
    2
    3
    4
    5
    6
    7
    [build-system]
    requires = [ # 依赖包
    "setuptools>=42",
    "wheel",
    "pybind11~=2.6.1"
    ]
    build-backend = "setuptools.build_meta"

准备完成后,可以在项目目录里面进行本地装包:

1
$ pip install . --user

显示安装成功即可在Python里面使用:

1
2
3
import example
example.add(1,2)
# 3

第二种方式是利用cmake编译出一个单独的项目.so,不用装包,比较轻便化。
如果没装cmake的话需要先装好。

示例目录如下:

1
2
3
4
5
6
.
├── CMakeLists.txt
└── src
├── bind.cpp
└── main.cpp

假如前面并没有安装pybind11,而是使用Github源码,则同样要放进目录里:

1
2
3
4
5
6
7
8
.
├── pybind11
│   └── ...
├── CMakeLists.txt
└── src
├── bind.cpp
└── main.cpp

  • src文件夹里的是C++实现代码,和上面说的一样;
  • CMakeLists.txt(名称不可变)是cmake的配置文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # cmake最低版本限制,实际上不写也行
    cmake_minimum_required(VERSION 3.15)

    # 项目名
    project(example)

    # 导入pybind
    # add_subdirectory(pybind11) # 假如使用pybind11本地源码,则写这行,否则写下面那行
    find_package(pybind11)

    # 导入接口文件
    pybind11_add_module(example src/bind.cpp)

准备好后,创建一个新文件夹来存放编译文件(为了不混乱):

1
2
3
4
$ mkdir build
$ cd build
$ cmake .. # 创建可编译文件
$ cmake --build . # 编译,相当于`make`命令

假如出现这个错误:

1
2
3
By not providing "Findpybind11.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "pybind11",
but CMake did not find one.

那就是因为pybind安装不完全(可能是使用了PyPI包),需要用别的方法装完整版或者扒Github源码。

一切顺利的话应该会达到进度100%:

1
2
3
[ 50%] Building CXX object CMakeFiles/example.dir/bind.cpp.o
[100%] Linking CXX shared module example.cpython-310-x86_64-linux-gnu.so
[100%] Built target example

然后它在build文件夹里面创建的那个example.*.so就是转码后文件,可以把他复制到你想要用的任何地方,在Python里面导入就行了:

1
2
3
import example
example.add(1,2)
# 3

注意这种方法并没有安装包,所以没有了那个example.*.so文件就会失败。