pybind11: C++ 工程提供 Python 接口的实例代码
C/C++ 工程提供 Python 接口,有利于融合进 Python 的生态。现在 Python 在应用层,有其得天独厚的优势。尤其因为人工智能和大数据的推波助澜, Python 现在以及未来,将长期是最流行的语言之一。
那 C/C++ 怎么提供 Python 接口呢?
- ctypes: C 与 Python 绑定, Python 内建模块
- Boost.Python: C++ 与 Python 绑定, Boost 模块
- pybind11: C++11 与 Python 绑定, 减去了旧 C++ 支持,更轻量化
本文将介绍 pybind11 的环境准备与入门使用。
pybind11: https://github.com/pybind/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.txt
里 add_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 接口的文章就介绍到这了,更多相关pybind11: C++ 工程如何提供 Python 接口内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!