python调用C++——pybind11
官方:https://pybind11.readthedocs.io/en/stable/ ;中文翻译:https://github.com/charlotteLive/pybind11-Chinese-docs
也是一种python调用c++的方式。
具体做法是 在C++代码里要写上要绑定的C++函数,封装、编译成某个python也能用的.so库,供给python代码import进这个库。
编译,可以用cmake。
例子,看官方文档就可以。
模型部署入门教程(四)阅读笔记一之Pybind11 保姆级教程
b站:模型部署入门教程(四)阅读笔记一之Pybind11 保姆级教程
该视频的例子来自youtube:Setting up CMake, Pybind11 and QtCreator [Part 3A, Understand & Code a Kalman Filter]
第0步 放第三方库
新建文件夹,记为thirdparty
,里面放第三方库,分别放了 eigen
和 pybind11
(分别是到官网https://eigen.tuxfamily.org/index.php?title=Main_Page
下载 eigen-3.4.0.zip 解压到该路径,和 git clone https://github.com/pybind/pybind11.git
)
(eigen是一个矩阵代数库)
第1步 创建 CMakeLists.txt
,内容为:
1 | # 项目名称 |
(这里kf来自一个kalman filter项目)
第2步 创建 wrapper.cpp
文件,内容为:
1 |
|
可以看到,这里cpp文件名叫wrapper,编译出的名称叫kf_cpp,不一样,如果一样就省事,如果不一样,则需要cmake编译
第3步 make,生成 kf_cpp
可执行动态库。
1 | mkdir build |
生成 kf_cpp.cpython-39-x86_64-linux-gnu.so
如果修改了
wrapper.cpp
文件,修改后,重新执行make
一下就好。如果一直cmake不好(比如修改过cmakelist.txt),就把build文件夹删除,重新创建一次。
第4步 python里测试
1 | import kf_cpp |
Setting up CMake, Pybind11 and QtCreator [Part 3A, Understand & Code a Kalman Filter]
youtube:Setting up CMake, Pybind11 and QtCreator [Part 3A, Understand & Code a Kalman Filter]
youtube:Python Wrappers for C++ with Pybind11 [Part 3B, Understand & Code a Kalman Filter]
youtube:Kalman Filter in C++ (and Pybind11) [Part 3C, Understand & Code a Kalman Filter]
全系列为:https://www.youtube.com/playlist?list=PLvKAPIGzFEr8n7WRx8RptZmC1rXeTzYtA
github:kalman_python_cpp
第一个视频和上面的b站视频基本一致。
- set up CMake, Eigen & Pybind11
- C++ Skeleton and integrate w/tests
- populate KF code in C++ Skeleton
第二、三个视频:
验证python里调用C++执行的结果,和python自己的函数执行的结果是否一致。用了命令行指令 pytest
(pytest 自动会测试以 test_
开头/结尾的文件,测试类以Test
开头,并且不能带有 init 方法,测试函数以test_
开头)
用一个wrapper.cpp作为绑定到python的中间人,里面写进其它cpp/h的类、函数,通过这个cpp来建立python和其它cpp/h之间的绑定:
wrapper.cpp
1 |
|
其中,类KF来自 kf.h
。
这里参考的是pybind11官方教程里的写法:
1 | py::class_<Pet>(m, "Pet") |
Python Bindings: Calling C or C++ From Python
Python 绑定:从 Python 调用 C 或 C++。
这篇没看很懂,有点乱。
编写绑定
在C++代码中,创建一些代码来告诉该工具如何构建您的 Python 绑定:
1 | // pybind11_wrapper.cpp |
注意这里,一般 PYBIND11_MODULE
的第一个参数 模块名,和这个cpp文件的名称相同最好??,就不需要自己写一个init了。
(上面这个代码例子,第一个参数模块名是pybind11_example
,但是cpp文件名称是pybind11_wrapper
)
让我们一次一个地看,因为PyBind11
将大量信息打包成几行。
前两行包括pybind11.h
C++ 库的文件和头文件cppmult.hpp
之后,你就有了PYBIND11_MODULE
宏。这将扩展为PyBind11
源代码中详细描述的 C++ 代码块:
此宏创建入口点,当 Python 解释器导入扩展模块时将调用该入口点。模块名称作为第一个参数给出,不应用引号引起来。第二个宏参数定义了一个
py::module
可用于初始化模块的类型变量。(来源)
在本例中,您正在==创建一个名为pybind11_example
的模块,其余代码将m
用作py::module
对象的名称==。在下一行,在您定义的 C++ 函数中,您为模块创建一个文档字符串。虽然这是可选的,但让您的模块更加Pythonic是一个不错的选择。
你有m.def()
call。这将定义一个由您的新 Python 绑定导出的函数,这意味着它将在 Python 中可见。在此示例中,您将传递三个参数:
- **
cpp_function
**是您将在 Python 中使用的函数的导出名称。如本例所示,它不需要匹配 C++ 函数的名称。(后面python调用函数,用的这个名称) &add
获取要导出的函数的地址。(C++函数名的地址)(这里是cppmult.hpp
里定义的一个函数add
)"A function..."
是函数的可选文档字符串。
现在您已经有了 Python 绑定的代码,接下来看看如何将其构建到 Python 模块中。
这里cppmult.hpp
是:
1 |
|
这里cppmult.cpp
是:
1 |
|
构建 Python 绑定
用于构建 Python 绑定的工具PyBind11
是 C++ 编译器本身。您可能需要修改编译器和操作系统的默认值。
首先,您必须构建要为其创建绑定的 C++ 库。对于这么小的示例,您可以将cppmult
库直接构建到 Python 绑定库中。但是,对于大多数实际示例,您将有一个要包装的预先存在的库,因此您将cppmult
单独构建该库。构建是对编译器的标准调用以构建共享库:
1 | # tasks.py |
运行这个invoke build-cppmult
产生libcppmult.so
:
1 | $ invoke build-cppmult |
另一方面,Python 绑定的构建需要一些特殊的细节:
1 | # tasks.py |
让我们逐行浏览一下。第 3 行包含相当标准的 C++ 编译器标志,指示几个细节,包括您希望捕获所有警告并将其视为错误、您需要共享库以及您使用的是 C++11。
第 4 行是魔法的第一步。它调用pybind11
模块使其include
为PyBind11
. 您可以直接在控制台上运行此命令以查看它的作用:
1 | $ python3 -m pybind11 --includes |
您的输出应该相似但显示不同的路径。
在编译调用的第 5 行,您可以看到您还添加了 Python dev 的路径includes
。虽然建议您不要链接 Python 库本身,但源代码需要一些代码Python.h
才能发挥其魔力。幸运的是,它使用的代码在 Python 版本中相当稳定。
第 5 行还用于-I .
将当前目录添加到include
路径列表中。这允许#include <cppmult.hpp>
解析包装器代码中的行。
第 6 行指定源文件的名称,即pybind11_wrapper.cpp
. 然后,在第 7 行,您会看到更多的构建魔法正在发生。此行指定输出文件的名称。Python 在模块命名上有一些特别的想法,包括 Python 版本、机器架构和其他细节。Python 还提供了一个工具来帮助解决这个问题python3-config
:
1 | $ python3-config --extension-suffix |
如果您使用的是不同版本的 Python,则可能需要修改该命令。如果您使用不同版本的 Python 或在不同的操作系统上,您的结果可能会发生变化。
构建命令的最后一行,第 8 行,将链接器指向libcppmult
您之前构建的库。该rpath
部分告诉链接器向共享库添加信息以帮助操作系统libcppmult
在运行时查找。最后,您会注意到此字符串的格式为cpp_name
和extension_name
。Cython
在下一节中构建 Python 绑定模块时,您将再次使用此函数。
运行此命令以构建绑定:
1 | $ invoke build-pybind11 |
就是这样!您已经使用PyBind11
. 是时候测试一下了!
调用你的函数
与CFFI
上面的示例类似,一旦您完成了创建 Python 绑定的繁重工作,调用您的函数看起来就像普通的 Python 代码:
1 | # pybind11_test.py |
由于您pybind11_example
在PYBIND11_MODULE
宏中用作模块的名称,因此这就是您导入的名称。在m.def()
您告诉PyBind11
将cppmult
函数导出为 的调用中cpp_function
,这就是您用来从 Python 调用它的方法。
你也可以测试它invoke
:
1 | $ invoke test-pybind11 |
这就是PyBind11
看起来的样子。接下来,您将了解何时以及为何PyBind11
是适合该工作的工具。
长处和短处
PyBind11
专注于 C++ 而不是 C,这使得它不同于ctypes
和CFFI
。它有几个特性使其对 C++ 库非常有吸引力:
- 它支持类。
- 它处理多态子类化。
- 它允许您从 Python 和许多其他工具向对象添加动态属性,而使用您检查过的基于 C 的工具很难做到这一点。
话虽如此,您需要进行大量设置和配置才能PyBind11
启动和运行。正确安装和构建可能有点挑剔,但一旦完成,它似乎相当可靠。此外,PyBind11
要求您至少使用 C++11 或更高版本。对于大多数项目来说,这不太可能是一个很大的限制,但它可能是您的一个考虑因素。
最后,创建 Python 绑定需要编写的额外代码是用 C++ 编写的,而不是用 Python 编写的。这可能是也可能不是你的问题,但它是比你在这里看到的其他工具不同。在下一节中,您将继续讨论Cython
,它采用完全不同的方法来解决这个问题。