Skip to main content

pybind11

C/C++ 工程提供 Python 接口,有利于融合进 Python 的生态。现在 Python 在应用层,有其得天独厚的优势。尤其因为人工智能和大数据的推波助澜, Python 现在以及未来,将长期是最流行的语言之一。

那 C/C++ 怎么提供 Python 接口呢?

  1. ctypes: C 与 Python 绑定, Python 内建模块
  2. Boost.Python: C++ 与 Python 绑定, Boost 模块
  3. pybind11: C++11 与 Python 绑定, 减去了旧 C++ 支持,更轻量化

本文将介绍 pybind11 的环境准备与入门使用。

环境准备#

pybind11 是一个 header-only 的库,换句话说,只需要 C++ 项目里直接 include pybind11 的头文件就能使用。

这里则介绍如何于 CMake 里引入 pybind11 。而更多编译系统的介绍,可见官方文档 Build systems

获取 pybind11#

可以 git submodule 添加子模块,最好固定为某个版本:

git submodule add https://github.com/pybind/pybind11.git third_party/pybind11-2.5.0cd third_party/pybind11-2.5.0/git checkout tags/v2.5.0

或者,直接获取源码,放进相应子目录即可。

添加进 CMake#

CMakeLists.txtadd_subdirectory pybind11 的路径,再用其提供的 pybind11_add_module 就能创建 pybind11 的模块了。

cmake_minimum_required(VERSION 3.1)project(start-pybind11 VERSION 0.1.0 LANGUAGES C CXX)
set(MY_PYBIND ${MY_CURR}/third_party/pybind11-2.5.0)
add_subdirectory(${MY_PYBIND})pybind11_add_module(example_pb example_pb.cpp)

如果想在已有 C++ 动态库上扩展 pybind11 绑定,那么 target_link_libraries 链接该动态库就可以了。

target_link_libraries(example_pb PUBLIC example)

绑定一个函数#

我们先实现一个 add 函数,

int add(int i, int j) {  return i + j;}

为了简化工程,可以直接实现在 example_pb.cpp 里,

#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int i, int j) {  return i + j;}
PYBIND11_MODULE(example_pb, m) {  m.doc() = "example_pb bindings";
  m.def("add", &add, "A function which adds two numbers");}

之后,于 CMakeLists.txt 所在目录,执行 cmake 编译就完成了。

示例代码#

绑定一个类#

我们先实现一个定时触发器的类。使用如下:

#include <iostream>
#include "tick.h"
int main(int argc, char const *argv[]) {  (void)argc;  (void)argv;
  Tick tick(500, 5000);
  tick.SetTickEvent([&tick](std::int64_t elapsed_ms) {    std::cout << "elapsed: " << elapsed_ms << " ms" << std::endl;    if (elapsed_ms >= 2000) {      tick.Stop();    }  });
  tick.Start();  tick.WaitLifeOver();  return 0;}

运行结果:

$ ./_output/bin/cpp_thread_callback/tick_testelapsed: 0 mselapsed: 500 mselapsed: 1000 mselapsed: 1500 mselapsed: 2000 ms

该类的声明如下:

using TickEvent = std::function<void(std::int64_t elapsed_ms)>;using TickRunCallback = std::function<void()>;
class Tick { public:  using clock = std::chrono::high_resolution_clock;
  Tick(std::int64_t tick_ms,       std::int64_t life_ms = std::numeric_limits<std::int64_t>::max());  Tick(TickEvent tick_event, std::int64_t tick_ms,       std::int64_t life_ms = std::numeric_limits<std::int64_t>::max(),       TickRunCallback run_beg = nullptr,       TickRunCallback run_end = nullptr);  virtual ~Tick();
  bool IsRunning() const;
  void Start();  void Stop(bool wait_life_over = false);
  const std::chrono::time_point<clock> &GetTimeStart() const;
  void SetTickEvent(TickEvent &&tick_event);  void SetTickEvent(const TickEvent &tick_event);
  void SetRunBegCallback(TickRunCallback &&run_beg);  void SetRunBegCallback(const TickRunCallback &run_beg);
  void SetRunEndCallback(TickRunCallback &&run_end);  void SetRunEndCallback(const TickRunCallback &run_end);
  void WaitLifeOver();
 protected:  // ...};

然后, pybind11 绑定实现如下:

#include <pybind11/pybind11.h>#include <pybind11/chrono.h>#include <pybind11/functional.h>
#include <memory>
#include "cpp/cpp_thread_callback/tick.h"
namespace py = pybind11;using namespace pybind11::literals;  // NOLINT
PYBIND11_MODULE(tick_pb, m) {  m.doc() = "tick_pb bindings";
  py::class_<Tick, std::shared_ptr<Tick>>(m, "Tick")    .def(py::init<std::int64_t, std::int64_t>())    .def(py::init<TickEvent, std::int64_t, std::int64_t,                  TickRunCallback, TickRunCallback>())    .def_property_readonly("is_running", &Tick::IsRunning)    .def("start", &Tick::Start)    .def("stop", &Tick::Stop, "wait_life_over"_a = false)    .def("get_time_start", &Tick::GetTimeStart)    .def("set_tick_event", [](Tick &self, const TickEvent &tick_event) {      self.SetTickEvent(tick_event);    })    .def("set_run_beg_callback", [](Tick &self,        const TickRunCallback &run_beg) {      self.SetRunBegCallback(run_beg);    })    .def("set_run_end_callback", [](Tick &self,        const TickRunCallback &run_end) {      self.SetRunEndCallback(run_end);    })    .def("wait_life_over", &Tick::WaitLifeOver,        py::call_guard<py::gil_scoped_release>());}

编译出动态库后,把路径添加进 PYTHONPATH

export PYTHONPATH=<path>:$PYTHONPATH
# 依赖其他动态库的话,把路径添加进 LIBRARY_PATH# Linuxexport LD_LIBRARY_PATH=<path>:$LD_LIBRARY_PATH# macOSexport DYLD_LIBRARY_PATH=<path>:$DYLD_LIBRARY_PATH

之后,就可以于 Python 里调用了:

#!/usr/bin/env python# -*- coding: utf-8 -*-# pylint: disable=missing-docstring, import-errorimport tick_pb as tick

def _main():  t = tick.Tick(lambda elapsed_ms: print(f"elapsed: {elapsed_ms} ms"),                500, 1000,                lambda: print("run beg"), lambda: print("run end"))  t.start()  t.wait_life_over()

if __name__ == "__main__":  _main()

运行结果:

$ python src/pybind/cpp_thread_callback/tick_test.pyrun begelapsed: 0 mselapsed: 500 mselapsed: 1000 msrun end

示例代码#

运行示例代码#

获取代码,

git clone https://github.com/ikuokuo/start-pybind11.git
# 获取子模块cd start-pybind11/git submodule update --init

编译安装,

# 依赖 cmake
cd start-pybind11/make install

编译结果,

$ tree _install_install├── bin│   └── cpp_thread_callback│       └── tick_test└── lib    ├── cpp_thread_callback    │   ├── libtick.0.1.0.dylib    │   ├── libtick.0.1.dylib -> libtick.0.1.0.dylib    │   ├── libtick.dylib -> libtick.0.1.dylib    │   ├── tick_pb.0.1.0.cpython-37m-darwin.so    │   ├── tick_pb.0.1.cpython-37m-darwin.so -> tick_pb.0.1.0.cpython-37m-darwin.so    │   └── tick_pb.cpython-37m-darwin.so -> tick_pb.0.1.cpython-37m-darwin.so    └── first_steps        ├── first_steps_pb.0.1.0.cpython-37m-darwin.so        ├── first_steps_pb.0.1.cpython-37m-darwin.so -> first_steps_pb.0.1.0.cpython-37m-darwin.so        ├── first_steps_pb.cpython-37m-darwin.so -> first_steps_pb.0.1.cpython-37m-darwin.so        ├── libfirst_steps.0.1.0.dylib        ├── libfirst_steps.0.1.dylib -> libfirst_steps.0.1.0.dylib        └── libfirst_steps.dylib -> libfirst_steps.0.1.dylib
5 directories, 13 files

添加路径,

$ source setup.bash first_steps cpp_thread_callbackDYLD_LIBRARY_PATH, PYTHONPATH+ /Users/John/Workspace/Self/ikuokuo/start-pybind11/_install/lib/first_steps+ /Users/John/Workspace/Self/ikuokuo/start-pybind11/_install/lib/cpp_thread_callback

运行示例,

$ python src/pybind/cpp_thread_callback/tick_test.pyrun begelapsed: 0 mselapsed: 500 mselapsed: 1000 msrun end