美文网首页GoCoding
pybind11: C++ 工程如何提供 Python 接口

pybind11: C++ 工程如何提供 Python 接口

作者: GoCodingInMyWay | 来源:发表于2020-09-04 06:42 被阅读0次

    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.0
    cd 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_test
    elapsed: 0 ms
    elapsed: 500 ms
    elapsed: 1000 ms
    elapsed: 1500 ms
    elapsed: 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
    # Linux
    export LD_LIBRARY_PATH=<path>:$LD_LIBRARY_PATH
    # macOS
    export DYLD_LIBRARY_PATH=<path>:$DYLD_LIBRARY_PATH
    

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

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # pylint: disable=missing-docstring, import-error
    import 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.py
    run beg
    elapsed: 0 ms
    elapsed: 500 ms
    elapsed: 1000 ms
    run 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_callback
    DYLD_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.py
    run beg
    elapsed: 0 ms
    elapsed: 500 ms
    elapsed: 1000 ms
    run end
    

    结语

    Go coding!

    相关文章

      网友评论

        本文标题:pybind11: C++ 工程如何提供 Python 接口

        本文链接:https://www.haomeiwen.com/subject/ngiasktx.html