在一些场景下,C++ 通过调用脚本语言实现一些功能会比用C++实现更加的方便。
这里要介绍的是pybind11,pybind11 借鉴了“前辈”Boost.Python,能够在 C++ 和 Python 之间自由转换,任意翻译两者的语言要素,比如把 C++ 的 vector 转换为 Python 的列表,把 Python 的元组转换为 C++ 的 tuple,既可以在 C++ 里调用 Python 脚本,也可以在 Python 里调用 C++ 的函数、类。
pybind11 名字里的“11”表示它完全基于现代 C++ 开发(C++11 以上),所以没有兼容旧系统的负担。它使用了大量的现代 C++ 特性,不仅代码干净整齐,运行效率也更高。
虽然 pybind11主要致力于使用 C + + 扩展 Python,但也可以反其道而行之: 将 Python 解释器嵌入到 C + + 程序中。
Python里调用c++的文章很多,这篇文章主要聚焦于如何在C++中调用Python。
前提
既然是和Python进行交互,那么肯定需要安装Python的,Linux系统下一般自带。
在C++中调用Python
只需几行 CMake 和 pybind11: :embed
就可以创建带有嵌入式解释器的基本可执行文件。
官方的CMake示例:
cmake_minimum_required(VERSION 3.4)
project(example)
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
add_executable(example main.cpp)
target_link_libraries(example PRIVATE pybind11::embed)
这里,我使用的是CMake的FetchContent更加方便
cmake_minimum_required(VERSION 3.20)
project(CppBindPythonDemo)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
)
FetchContent_MakeAvailable(pybind11)
add_executable(CppBindPythonDemo main.cpp)
target_link_libraries(CppBindPythonDemo PRIVATE pybind11::embed)
执行Python代码
运行 Python 代码有几种不同的方法。一种选择是使用 eval
、 exec
或 eval _ file
hello_world:
//main.cpp
#include <pybind11/embed.h> // everything needed for embedding
namespace py = pybind11;
int main() {
py::scoped_interpreter guard{}; // start the interpreter and keep it alive
py::print("Hello, World!"); // use the Python API
}
示例1:
#include <pybind11/embed.h>
namespace py = pybind11;
int main() {
py::scoped_interpreter guard{};
py::exec(R"(
kwargs = dict(name="World", number=42)
message = "Hello, {name}! The answer is {number}".format(**kwargs)
print(message)
)");
}
示例2:
#include <pybind11/embed.h>
namespace py = pybind11;
using namespace py::literals;
int main() {
py::scoped_interpreter guard{};
auto kwargs = py::dict("name"_a="World", "number"_a=42);
auto message = "Hello, {name}! The answer is {number}"_s.format(**kwargs);
py::print(message);
}
示例3:
#include <pybind11/embed.h>
#include <iostream>
namespace py = pybind11;
using namespace py::literals;
int main() {
py::scoped_interpreter guard{};
auto locals = py::dict("name"_a="World", "number"_a=42);
py::exec(R"(
message = "Hello, {name}! The answer is {number}".format(**locals())
)", py::globals(), locals);
auto message = locals["message"].cast<std::string>();
std::cout << message;
}
可以看到,有一个共同点,在执行Python代码之前,都会先创建py::scoped_interpreter guard{};
在使用任何 Python API (包括 pybind11中的所有函数和类)之前,必须对解释器进行初始化。 scoped_interpreter
RAII 类负责解释器的生命周期。scoped_interpreter
生命周期结束后,解释器关闭并清理内存,在此之后就不能调用 Python 函数了。
导入模块
除了上述的执行方式外,Python模块也可以导入,系统模块或者是自己写的都可以。
调用系统模块:
py::module_ sys = py::module_::import("sys");
py::print(sys.attr("path"));
py::module math = py::module::import("math");
py::object result = math.attr("sqrt")(25);
std::cout << "Sqrt of 25 is: " << result.cast<float>() << std::endl;
调用自己编写的模块(即py文件)
这里我写了一个py_bind_demo.py
文件,里面有一个函数fib
和一个类PyClass
#py_bind_demo.py
def fib(n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
class PyClass:
__name = "py_module"
def print_name(self):
print("call python print_name func")
print(self.__name)
def fib(self, n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
接下来,在main.cpp
中调用
//用py::object导入python类模块并进行类的初始化
py::object PyClass = py::module::import("py_bind_demo").attr("PyClass");
//创建类对象
py::object pyClass = PyClass();
//调用类成员函数
pyClass.attr("print_name")();
pyClass.attr("fib")(4);
编译运行,结果报错
terminate called after throwing an instance of 'pybind11::error_already_set'
what(): ModuleNotFoundError: No module named 'py_bind_demo'
提示找不到此模块,为什么呢?
经过分析后发现,是因为目录结构的问题,我的main.cpp
和py_bind_demo.py
在同一路径下,而不是py文件在可执行文件路径下,故运行可执行文件时,提示找不到模块。
所以,当调用Python时,需要将py文件放入可执行文件路径下
如图:
之前 | 之后 |
---|---|
这下就可以成功运行了
总结
本文简单介绍了如何在C++中调用Python的方法,还有很多细节并未展开,可自行查询相关文档。
pybind11: Seamless operability between C++11 and Python (github.com)
pybind11 documentation