13. 内嵌解释器

虽然pybind11主要聚焦于使用C++扩展Python,反过来也是可以的,可以内嵌Python解释器到C++程序中。前面章节讲解的pybind11内容仍然适用。本节将介绍嵌入所需的一些额外内容。

13.1 准备开始

创建一个内嵌解释器的程序,可以在Cmake中添加pybind11::embed来支持。

  1. cmake_minimum_required(VERSION 3.4)
  2. project(example)
  3. find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
  4. add_executable(example main.cpp)
  5. target_link_libraries(example PRIVATE pybind11::embed)

main.cpp的基本结构如下:

  1. #include <pybind11/embed.h> // everything needed for embedding
  2. namespace py = pybind11;
  3. int main() {
  4. py::scoped_interpreter guard{}; // start the interpreter and keep it alive
  5. py::print("Hello, World!"); // use the Python API
  6. }

需要在使用任意Python API前初始化解释器,包括pybind11 Python函数和类。RAII guard类scoped_interpreter可用来管理解释器的生命周期。在guard类销毁时,解释器将会关闭并占用的内存。必须在所有Python函数前调用它。

13.2 执行Python代码

在12.3.3节中,我们介绍了可以使用evalexeceval_file函数来执行Python表达式或语句。下面的例子展示了附带解释器来执行Python代码的流程:

  1. #include <pybind11/embed.h>
  2. namespace py = pybind11;
  3. int main() {
  4. py::scoped_interpreter guard{};
  5. py::exec(R"(
  6. kwargs = dict(name="World", number=42)
  7. message = "Hello, {name}! The answer is {number}".format(**kwargs)
  8. print(message)
  9. )");
  10. }

也可以使用pybind11 API来实现相同的功能(参考12章)。

  1. #include <pybind11/embed.h>
  2. namespace py = pybind11;
  3. using namespace py::literals;
  4. int main() {
  5. py::scoped_interpreter guard{};
  6. auto kwargs = py::dict("name"_a="World", "number"_a=42);
  7. auto message = "Hello, {name}! The answer is {number}"_s.format(**kwargs);
  8. py::print(message);
  9. }

两种方法也可以混合使用:

  1. #include <pybind11/embed.h>
  2. #include <iostream>
  3. namespace py = pybind11;
  4. using namespace py::literals;
  5. int main() {
  6. py::scoped_interpreter guard{};
  7. auto locals = py::dict("name"_a="World", "number"_a=42);
  8. py::exec(R"(
  9. message = "Hello, {name}! The answer is {number}".format(**locals())
  10. )", py::globals(), locals);
  11. auto message = locals["message"].cast<std::string>();
  12. std::cout << message;
  13. }

12.3 导入模块

使用module_::import()可以导入Python模块。

  1. py::module_ sys = py::module_::import("sys");
  2. py::print(sys.attr("path"));

为方便起见,内嵌解释器时,会将当前工作路径包含到sys.path中。这样我们可以方便地导入本地Python文件。

  1. """calc.py located in the working directory"""
  2. def add(i, j):
  3. return i + j
  1. py::module_ calc = py::module_::import("calc");
  2. py::object result = calc.attr("add")(1, 2);
  3. int n = result.cast<int>();
  4. assert(n == 3);

如果运行时源文件被修改(如被外部进程修改),可以使用module_::reload()重新导入模块。这在下面的场景中十分有用:有个应用程序要导入用户定义数据处理脚本,该脚本需要在用户修改后更新时。注意,这个函数不会递归地重新加载模块。

12.4 添加内嵌模块

使用宏PYBIND11_EMBEDDED_MODULE可以添加内嵌的二进制模块。这个定义需要放在全局作用域中。定义后,他们可以向其他模块一样导入。

  1. #include <pybind11/embed.h>
  2. namespace py = pybind11;
  3. PYBIND11_EMBEDDED_MODULE(fast_calc, m) {
  4. // `m` is a `py::module_` which is used to bind functions and classes
  5. m.def("add", [](int i, int j) {
  6. return i + j;
  7. });
  8. }
  9. int main() {
  10. py::scoped_interpreter guard{};
  11. auto fast_calc = py::module_::import("fast_calc");
  12. auto result = fast_calc.attr("add")(1, 2).cast<int>();
  13. assert(result == 3);
  14. }

Unlike extension modules where only a single binary module can be created, on the embedded side an unlimited number of modules can be added using multiple PYBIND11_EMBEDDED_MODULE definitions (as long as they have unique names).

These modules are added to Python’s list of builtins, so they can also be imported in pure Python files loaded by the interpreter. Everything interacts naturally:

  1. """py_module.py located in the working directory"""
  2. import cpp_module
  3. a = cpp_module.a
  4. b = a + 1
  5. #include <pybind11/embed.h>
  6. namespace py = pybind11;
  7. PYBIND11_EMBEDDED_MODULE(cpp_module, m) {
  8. m.attr("a") = 1;
  9. }
  10. int main() {
  11. py::scoped_interpreter guard{};
  12. auto py_module = py::module_::import("py_module");
  13. auto locals = py::dict("fmt"_a="{} + {} = {}", **py_module.attr("__dict__"));
  14. assert(locals["a"].cast<int>() == 1);
  15. assert(locals["b"].cast<int>() == 2);
  16. py::exec(R"(
  17. c = a + b
  18. message = fmt.format(a, b, c)
  19. )", py::globals(), locals);
  20. assert(locals["c"].cast<int>() == 3);
  21. assert(locals["message"].cast<std::string>() == "1 + 2 = 3");
  22. }

12.5 解释器的生命周期

scoped_interpreter 销毁时,程序会自动关闭Python解释器。后面再创建一个新的示例会重启解释器。或者,我们也可以使用 initialize_interpreter / finalize_interpreter 这组函数在任意时刻直接设置解释器状态。

解释器重启后,pybind11创建的模块可以安全地重新初始化,但第三方扩展模块可能会有些问题。问题在于Python本身不能完全卸载扩展模块,并且会有一些解释器重启的警告。简而言之,由于Python引用循环或用户创建的全局数据,并非所有内存都可能被释放。具体细节可以查看CPython文档。

Warning

Creating two concurrent scoped_interpreter guards is a fatal error. So is calling initialize_interpreter for a second time after the interpreter has already been initialized.

Do not use the raw CPython API functions Py_Initialize and Py_Finalize as these do not properly handle the lifetime of pybind11’s internal data.