第28章 嵌入Python解释器
嵌入(Embedding)是将 Python 解释器集成到 C++ 程序中的技术。这是绑定的逆过程——我们不是把 C++ 代码暴露给 Python,而是把 Python 解释器嵌入到 C++ 应用中。
28.1 嵌入基础
Section titled “28.1 嵌入基础”为什么要嵌入 Python 解释器?常见的应用场景包括:
- 插件系统:允许用户使用 Python 编写插件扩展应用功能
- 脚本引擎:为游戏或仿真器提供脚本能力
- 测试框架:在 C++ 程序中运行 Python 测试代码
- 配置系统:使用 Python 脚本作为配置文件
嵌入与绑定的核心区别:
| 方向 | 绑定(Binding) | 嵌入(Embedding) |
|---|---|---|
| Python → C++ | Python 调用 C++ | C++ 调用 Python |
| 入口 | PYBIND11_MODULE | Py_Initialize |
| 目的 | 暴露 C++ 给 Python | 在 C++ 中运行 Python |
28.2 初始化与配置
Section titled “28.2 初始化与配置”使用 Py_Initialize() 和 Py_Finalize() 管理 Python 解释器生命周期。
#include <pybind11/embed.h>#include <iostream>#include <string>
namespace py = pybind11;
int main(int argc, char* argv[]) { // 初始化 Python 解释器 // 必须在使用任何 Python API 之前调用 py::scoped_interpreter guard{};
// 可选:传递命令行参数给 Python PySys_SetArgv(argc, argv);
// 现在可以执行 Python 代码 std::cout << "Python interpreter embedded successfully!" << std::endl;
// Python 解释器在 guard 对象销毁时自动清理 return 0;}// 更细粒度的控制:手动初始化和关闭#include <pybind11/embed.h>#include <iostream>
namespace py = pybind11;
int main() { // 检查是否需要初始化 if (!Py_IsInitialized()) { std::cout << "Initializing Python..." << std::endl; Py_Initialize(); }
// 检查初始化状态 if (Py_IsInitialized()) { std::cout << "Python interpreter ready" << std::endl;
// 执行一些代码 py::exec("print('Hello from embedded Python!')");
// 关闭解释器 Py_Finalize(); std::cout << "Python interpreter closed" << std::endl; } else { std::cerr << "Failed to initialize Python" << std::endl; return 1; }
return 0;}关键洞察:
py::scoped_interpreter是最安全的初始化方式——它使用 RAII 模式确保解释器正确关闭。在生产代码中,优先使用这种方式。
28.3 执行Python代码
Section titled “28.3 执行Python代码”使用 py::exec 和 py::eval 在 C++ 中执行 Python 代码。
#include <pybind11/embed.h>#include <iostream>
namespace py = pybind11;
int main() { py::scoped_interpreter guard{};
// 执行单行代码 py::exec("result = 2 + 3"); py::exec("print('2 + 3 =', result)");
// 执行包含控制流的代码块 py::exec(R"(for i in range(5): print(f"iteration {i}"))");
// 求值单个表达式 py::object result = py::eval("42 * 2"); std::cout << "42 * 2 = " << py::cast<int>(result) << std::endl;
// 求值带作用域的表达式 py::object locals = py::dict(); py::exec("x = 100", py::globals(), locals); py::object x = py::eval("x * 2", py::globals(), locals); std::cout << "x * 2 = " << py::cast<int>(x) << std::endl;
return 0;}// 使用 py::module_ 执行模块级代码#include <pybind11/embed.h>#include <iostream>
namespace py = pybind11;
int main() { py::scoped_interpreter guard{};
// 获取 builtins 模块 py::object builtins = py::module_::import("builtins"); py::object result = builtins.attr("sum")(py::make_tuple(1, 2, 3, 4, 5)); std::cout << "sum([1,2,3,4,5]) = " << py::cast<int>(result) << std::endl;
// 使用 py::exec_file 执行 Python 文件 // py::exec_file("script.py");
return 0;}关键洞察:
py::eval用于表达式(返回结果),py::exec用于语句(无返回值)。两者都可以接受py::globals()和py::dict()作为作用域参数来控制变量访问。
28.4 双向C++/Python通信
Section titled “28.4 双向C++/Python通信”实现 C++ 和 Python 之间的数据传递和函数调用。
#include <pybind11/embed.h>#include <iostream>#include <string>#include <vector>
namespace py = pybind11;
// C++ 函数:可以被 Python 调用int add_numbers(int a, int b) { return a + b;}
// C++ 类:可以被 Python 使用class Calculator {public: Calculator() = default;
double compute(double x, double y, const std::string& op) { if (op == "add") return x + y; if (op == "sub") return x - y; if (op == "mul") return x * y; if (op == "div" && y != 0) return x / y; return 0; }};
PYBIND11_EMBEDDED_MODULE(embedded_module, m) { // 导出 C++ 函数到 Python m.def("add", &add_numbers, "Add two integers");
// 导出 C++ 类到 Python py::class_<Calculator>(m, "Calculator") .def(py::init<>()) .def("compute", &Calculator::compute, "Perform calculation");}
int main() { py::scoped_interpreter guard{};
// 导入嵌入式模块(在 C++ 中注册) py::module_ embedded_module = py::module_::import("embedded_module");
// 从 C++ 调用 Python 函数 py::object python_func = py::eval("lambda x: x * 2"); py::object cpp_result = python_func(42); std::cout << "Python lambda(42) = " << py::cast<int>(cpp_result) << std::endl;
// 使用 C++ 导出给 Python 的函数 py::object cpp_result2 = embedded_module.attr("add")(10, 20); std::cout << "C++ add(10, 20) = " << py::cast<int>(cpp_result2) << std::endl;
// 使用 C++ 导出给 Python 的类 py::object calc = embedded_module.attr("Calculator")(); py::object result = calc.attr("compute")(10.0, 3.0, py::str("mul")); std::cout << "Calculator.compute(10, 3, 'mul') = " << py::cast<double>(result) << std::endl;
// 从 C++ 设置 Python 全局变量 py::globals()["cpp_var"] = py::int_(42); py::exec("print('cpp_var from Python:', cpp_var)");
// 从 Python 获取变量到 C++ py::exec("python_result = 123"); int result_value = py::cast<int>(py::globals()["python_result"]); std::cout << "python_result from C++ = " << result_value << std::endl;
// 传递复杂数据结构 py::list cpp_list = py::cast(std::vector<int>{1, 2, 3, 4, 5}); py::exec("print('List from C++:', cpp_list)", py::globals(), py::dict(py::arg("cpp_list") = cpp_list));
return 0;}import embedded_module
result = embedded_module.add(3, 4)print(f"3 + 4 = {result}")
calc = embedded_module.Calculator()print(f"10 + 5 = {calc.compute(10, 5, 'add')}")print(f"10 - 5 = {calc.compute(10, 5, 'sub')}")print(f"10 * 5 = {calc.compute(10, 5, 'mul')}")print(f"10 / 5 = {calc.compute(10, 5, 'div')}")关键洞察:嵌入模式下的双向通信需要仔细管理对象生命周期。使用
py::cast<T>()和py::make_tuple()/py::dict()等工具在不同语言之间传递数据。
28.5 应用场景分析
Section titled “28.5 应用场景分析”28.5.1 插件系统
Section titled “28.5.1 插件系统”#include <pybind11/embed.h>#include <iostream>#include <map>#include <string>#include <memory>
namespace py = pybind11;
// 插件接口class IPlugin {public: virtual ~IPlugin() = default; virtual std::string name() const = 0; virtual void execute() = 0;};
// 插件管理器class PluginManager {public: void load_plugin(const std::string& plugin_path) { py::object importlib = py::module_::import("importlib.util"); py::object spec = importlib.attr("spec_from_file_location")( "plugin_module", py::str(plugin_path)); py::object module = importlib.attr("module_from_spec")(spec); spec.attr("loader").attr("exec_module")(module);
// 获取插件类并实例化 py::object plugin_class = module.attr("Plugin"); py::object plugin_instance = plugin_class(); plugins_[plugin_instance.attr("name").cast<std::string>()] = plugin_instance; }
void execute_plugin(const std::string& name) { if (plugins_.contains(name)) { plugins_[name].attr("run")(); } }
void list_plugins() { py::exec("print('Loaded plugins:', list(plugins.keys()))", py::dict(py::arg("plugins") = py::cast(plugins_))); }
private: std::map<std::string, py::object> plugins_;};class Plugin: name = "ExamplePlugin" version = "1.0.0"
def run(self): print(f"Running {self.name} v{self.version}")
def process(self, data): return f"Processed: {data}"28.5.2 脚本引擎
Section titled “28.5.2 脚本引擎”#include <pybind11/embed.h>#include <iostream>#include <string>#include <fstream>#include <sstream>
namespace py = pybind11;
class ScriptEngine {public: ScriptEngine() { py::exec(R"(import mathfrom typing import List
class ScriptEngine: def __init__(self): self.variables = {}
def set_variable(self, name: str, value: float) -> None: self.variables[name] = value
def get_variable(self, name: str) -> float: return self.variables.get(name, 0.0)
def evaluate(self, expr: str) -> float: # 安全评估数学表达式 allowed_names = {**math.__dict__, **self.variables} return eval(expr, {"__builtins__": {}}, allowed_names))"); }
void set_variable(const std::string& name, double value) { py::exec(R"(scope = {'name': name, 'value': value}exec(f'{name} = {value}', scope))", py::dict(py::arg("name") = py::str(name), py::arg("value") = py::float_(value))); }
double evaluate(const std::string& expression) { py::object result = py::eval(expression); return result.cast<double>(); }
private: py::object engine_;};
// 简化的数学表达式求值double eval_math(const std::string& expr, const std::map<std::string, double>& vars) { py::scoped_interpreter guard{};
// 设置变量到 Python 空间 py::dict local_dict; for (const auto& [k, v] : vars) { local_dict[k.c_str()] = py::float_(v); }
py::object result = py::eval(expr.c_str(), py::dict(), local_dict); return result.cast<double>();}28.5.3 测试框架
Section titled “28.5.3 测试框架”#include <pybind11/embed.h>#include <iostream>#include <string>#include <vector>
namespace py = pybind11;
struct TestResult { std::string name; bool passed; std::string message;};
class PythonTestRunner {public: PythonTestRunner() { // 配置 pytest py::exec(R"(import sys
def run_tests(): import pytest # 配置 pytest 输出 pytest.main(['-v', '--tb=short']))"); }
void run_test_file(const std::string& test_file) { py::module_ pytest = py::module_::import("pytest"); pytest.attr("main")(py::make_tuple("-v", "--tb=short", "-s", test_file.c_str())); }
std::vector<TestResult> discover_tests(const std::string& module_path) { py::object pytest = py::module_::import("pytest"); py::object collector = pytest.attr("Collector");
std::vector<TestResult> results; // 发现和运行测试的逻辑 return results; }};
// 在 C++ 中使用 pytestint main(int argc, char* argv[]) { py::scoped_interpreter guard{};
PythonTestRunner runner;
// 运行指定的测试文件 if (argc > 1) { std::cout << "Running tests from: " << argv[1] << std::endl; runner.run_test_file(argv[1]); }
return 0;}关键洞察:嵌入 Python 为 C++ 应用提供了极大的灵活性。插件系统允许用户扩展应用功能,脚本引擎让非 C++ 开发者也能参与业务逻辑,测试框架则能复用 Python 丰富的测试工具生态。
嵌入Python总结:
| 组件 | 说明 |
|---|---|
py::scoped_interpreter | RAII 风格的解释器初始化 |
py::exec() | 执行 Python 语句块 |
py::eval() | 求值 Python 表达式 |
py::module_::import() | 导入 Python 模块 |
py::globals() | 访问 Python 全局命名空间 |
py::dict() | 创建 Python 字典 |
PYBIND11_EMBEDDED_MODULE | 在 C++ 中注册模块供 Python 导入 |
最佳实践:
- 始终使用
py::scoped_interpreter管理生命周期 - 在多线程环境中注意 GIL 管理
- 使用
py::gil_scoped_release释放 GIL 以提高性能 - 避免在 Python 代码中执行耗时的 CPU 密集型操作