pybind11内嵌解释器
一、创建解释器
需要在使用任意Python API前初始化解释器,包括pybind11 Python函数和类。RAII guard类`scoped_interpreter`可用来管理解释器的生命周期。在guard类销毁时,解释器将会关闭并占用的内存。必须在所有Python函数前调用它。
#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 }
也可以使用pybind11 API来实现相同的功能:(参见PythonC++接口)
#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) )"); }
#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); }
两种方法也可以混合使用:
#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; }
二、导入模块
使用`module_::import()`可以导入Python模块。
py::module_ sys = py::module_::import("sys"); py::print(sys.attr("path"));
为方便起见,内嵌解释器时,会将当前工作路径包含到`sys.path`中。这样我们可以方便地导入本地Python文件。
```python """calc.py located in the working directory""" def add(i, j): return i + j ``` ```c++ py::module_ calc = py::module_::import("calc"); py::object result = calc.attr("add")(1, 2); int n = result.cast<int>(); assert(n == 3); ```
如果运行时源文件被修改(如被外部进程修改),可以使用`module_::reload()`重新导入模块。这在下面的场景中十分有用:有个应用程序要导入用户定义数据处理脚本,该脚本需要在用户修改后更新时。注意,这个函数不会递归地重新加载模块。
三、添加内嵌模块
使用宏`PYBIND11_EMBEDDED_MODULE`可以添加内嵌的二进制模块。这个定义需要放在全局作用域中。定义后,他们可以向其他模块一样导入。
#include <pybind11/embed.h> namespace py = pybind11; PYBIND11_EMBEDDED_MODULE(fast_calc, m) { // `m` is a `py::module_` which is used to bind functions and classes m.def("add", [](int i, int j) { return i + j; }); } int main() { py::scoped_interpreter guard{}; auto fast_calc = py::module_::import("fast_calc"); auto result = fast_calc.attr("add")(1, 2).cast<int>(); assert(result == 3); }
与只能创建单个二进制模块的扩展模块不同,在嵌入式方面,可以使用多个“PYBIND11_embedded_module”定义添加无限数量的模块(只要它们具有唯一的名称)。这些模块被添加到Python的内置列表中,因此它们也可以导入到解释器加载的纯Python文件中。一切自然互动:
"""py_module.py located in the working directory""" import cpp_module a = cpp_module.a b = a + 1 #include <pybind11/embed.h> namespace py = pybind11; PYBIND11_EMBEDDED_MODULE(cpp_module, m) { m.attr("a") = 1; } int main() { py::scoped_interpreter guard{}; auto py_module = py::module_::import("py_module"); auto locals = py::dict("fmt"_a="{} + {} = {}", **py_module.attr("__dict__")); assert(locals["a"].cast<int>() == 1); assert(locals["b"].cast<int>() == 2); py::exec(R"( c = a + b message = fmt.format(a, b, c) )", py::globals(), locals); assert(locals["c"].cast<int>() == 3); assert(locals["message"].cast<std::string>() == "1 + 2 = 3"); }
四、解释器的生命周期
当 `scoped_interpreter` 销毁时,程序会自动关闭Python解释器。后面再创建一个新的示例会重启解释器。或者,我们也可以使用 `initialize_interpreter` / `finalize_interpreter` 这组函数在任意时刻直接设置解释器状态。
解释器重启后,pybind11创建的模块可以安全地重新初始化,但第三方扩展模块可能会有些问题。问题在于Python本身不能完全卸载扩展模块,并且会有一些解释器重启的警告。简而言之,由于Python引用循环或用户创建的全局数据,并非所有内存都可能被释放。具体细节可以查看CPython文档。