第19章 调用Python代码
pybind11 允许在 C++ 代码中调用 Python 函数、导入模块、执行代码字符串,实现 C++ 与 Python 间的双向交互。
19.1 调用Python函数
Section titled “19.1 调用Python函数”通过 py::function 调用 Python 可调用对象。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// 接收 Python 函数作为参数py::object call_function(py::function func, py::object arg) { return func(arg);}
py::object call_function_two_args(py::function func, py::object arg1, py::object arg2) { return func(arg1, arg2);}
// 模板化调用:支持任意参数列表py::object call_py_func(py::function func, py::args args, py::kwargs kwargs) { return func(*args, **kwargs);}
// 使用 py::eval 调用 lambdapy::object apply_transform(py::object transform, py::object value) { return transform(value);}
PYBIND11_MODULE(call_python_demo, m) { m.def("call_once", &call_function, py::arg("func"), py::arg("arg")); m.def("call_with_two_args", &call_function_two_args, py::arg("func"), py::arg("arg1"), py::arg("arg2"));
m.def("apply", [](py::function f, py::object x) { return f(x); });
m.def("apply_transform", &apply_transform, py::arg("transform"), py::arg("value"));}>>> import call_python_demo as m
>>> # 调用 Python 函数>>> double = lambda x: x * 2>>> m.call_once(double, 5)10
>>> add = lambda x, y: x + y>>> m.call_with_two_args(add, 3, 7)10
>>> # 使用 apply>>> m.apply(lambda x: x ** 2, 4)16
>>> # 链式转换>>> step1 = lambda x: x + 1>>> step2 = lambda x: x * 2>>> m.apply_transform(step2, m.apply_transform(step1, 3))8关键洞察:
py::function是 Python 可调用对象的包装。通过直接调用func(arg1, arg2, ...)或func(*args, **kwargs)传递参数。
19.2 参数构造
Section titled “19.2 参数构造”构建 Python 函数调用参数。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// 方式1:直接传递 py::objectvoid direct_args() { py::object result = py::eval("lambda x, y: x + y")(py::int_(1), py::int_(2)); py::print("Result:", result);}
// 方式2:使用 py::make_tuple 构建参数元组void tuple_args() { py::object func = py::eval("lambda args: sum(args)"); py::tuple args = py::make_tuple(1, 2, 3, 4, 5);
// 解包元组作为参数 py::object result = py::reinterpret_steal<py::object>( PyObject_Call(func.ptr(), args.ptr(), nullptr)); py::print("Sum:", result);}
// 方式3:构建关键字参数 py::dictvoid keyword_args() { py::object func = py::eval("lambda a, b, c: a * b + c"); py::dict kwargs = py::dict("a"_a = 2, "b"_a = 3, "c"_a = 1);
py::object result = func(py::int_(10), py::int_(20), py::int_(30)); py::print("Result:", result);}
// 方式4:组合位置参数和关键字参数py::object mixed_call(py::function func, py::list positional, py::dict keywords) { py::tuple args = py::tuple(py::cast(positional)); return py::reinterpret_steal<py::object>( PyObject_Call(func.ptr(), args.ptr(), keywords.ptr()));}
PYBIND11_MODULE(param_demo, m) { m.def("call_with_tuple", [](py::function func, py::tuple args) { return py::reinterpret_steal<py::object>( PyObject_Call(func.ptr(), args.ptr(), nullptr)); });
m.def("mixed_call", &mixed_call);
m.def("build_kwargs", []() { return py::dict("name"_a = "test", "value"_a = 42); });}>>> import param_demo as m
>>> # 使用元组传参>>> func = lambda x, y, z: x + y + z>>> args = (1, 2, 3)>>> m.call_with_tuple(func, args)6
>>> # 混合参数>>> f = lambda a, b, c=0: a + b + c>>> m.mixed_call(f, [1, 2], {"c": 3})6
>>> # 构建关键字参数>>> m.build_kwargs(){'name': 'test', 'value': 42}关键洞察:Python 函数调用参数可以通过
py::tuple构建位置参数,通过py::dict构建关键字参数。使用PyObject_Call支持混合传参。
19.3 返回值处理
Section titled “19.3 返回值处理”处理 Python 函数返回值。
#include <pybind11/pybind11.h>#include <string>#include <vector>#include <stdexcept>
namespace py = pybind11;
// 简单返回值py::object simple_return() { return py::str("returned string");}
// 无返回值(返回 None)py::object returns_none() { return py::none();}
// Python 对象作为返回值py::object return_list() { return py::list(py::make_tuple(1, 2, 3));}
py::object return_dict() { return py::dict("key"_a = "value", "num"_a = 42);}
// 处理返回对象的属性访问py::object process_returned_object() { py::object result = py::eval("type('MyClass', (), {'x': 10, 'get_data': lambda self: self.x * 2})"); py::object obj = py::eval("type('MyClass', (), {'x': 10, 'get_data': lambda self: self.x * 2})")();
// 访问属性 int x = py::cast<int>(obj.attr("x")); int data = py::cast<int>(obj.attr("get_data")());
return py::make_tuple(x, data);}
// 类型检查后转换py::object safe_extract_value(py::object obj) { if (py::isinstance<py::int_>(obj)) { return py::cast(py::cast<int>(obj) * 10); } else if (py::isinstance<py::str>(obj)) { return py::str("processed: " + py::cast<std::string>(obj)); } else if (py::isinstance<py::list>(obj)) { py::list lst = py::cast<py::list>(obj); return py::cast(py::len(lst)); } return py::none();}
PYBIND11_MODULE(return_demo, m) { m.def("simple", &simple_return); m.def("none", &returns_none); m.def("get_list", &return_list); m.def("get_dict", &return_dict); m.def("process_object", &process_returned_object); m.def("safe_extract", &safe_extract_value);}>>> import return_demo as m
>>> m.simple()'returned string'
>>> m.none()
>>> m.get_list()[1, 2, 3]
>>> m.get_dict(){'key': 'value', 'num': 42}
>>> m.process_object()(10, 20)
>>> # 安全提取>>> m.safe_extract(5)50>>> m.safe_extract("hello")'processed: hello'>>> m.safe_extract([1, 2, 3])3>>> m.safe_extract({})0关键洞察:Python 函数返回值是
py::object类型。使用py::isinstance<T>()检查类型,使用py::cast<T>()提取 C++ 值。使用py::len()获取 Python 容器长度。
19.4 异常处理
Section titled “19.4 异常处理”捕获和处理 Python 抛出的异常。
#include <pybind11/pybind11.h>#include <string>#include <stdexcept>
namespace py = pybind11;
// 正常执行void normal_execution() { py::print("This works fine");}
// 抛出 Python 异常(被 C++ 捕获)void throw_py_exception() { try { // 模拟调用 Python 代码 py::object result = py::eval("raise ValueError('python error')"); } catch (const py::error_already_set& e) { // py::error_already_set 包含 Python 异常信息 py::print("Caught Python exception:", e.what()); }}
// 抛出 C++ 异常(传播到 Python)void throw_cpp_exception() { throw std::runtime_error("C++ runtime error");}
// 在 Python 异常后恢复void handle_and_recover() { try { py::eval("1/0"); // ZeroDivisionError } catch (const py::error_already_set& e) { py::print("Handling:", e.what()); // 清除异常状态,允许后续操作继续 e.restore(); }}
// 捕获特定类型异常void catch_specific() { try { py::eval("raise TypeError('type mismatch')"); } catch (const py::error_already_set& e) { // 获取异常类型 py::object type = e.type(); py::object value = e.value(); py::print("Exception type:", py::str(type.attr("__name__"))); py::print("Exception value:", value); }}
PYBIND11_MODULE(exception_demo, m) { m.def("normal", &normal_execution); m.def("cpp_throws", &throw_cpp_exception); m.def("handle_py_exception", &throw_py_exception); m.def("handle_and_recover", &handle_and_recover); m.def("catch_specific", &catch_specific);
m.def("rethrow_cpp", []() { // 从 C++ 抛出,会被 Python 捕获 throw std::runtime_error("error from C++"); });}>>> import exception_demo as m
>>> m.normal()This works fine
>>> # C++ 抛出的异常>>> m.cpp_throws()Traceback (most recent call last): RuntimeError: C++ runtime error
>>> # Python 异常在 C++ 中被捕获>>> m.handle_py_exception()Caught Python exception: python error
>>> # 恢复后继续执行>>> m.handle_and_recover()Handling: division by zero
>>> # 捕获特定异常>>> m.catch_specific()Exception type: TypeErrorException value: type mismatch关键洞察:
py::error_already_set是 Python 异常的包装。捕获后可通过.what()获取信息,使用.restore()恢复异常状态。throw语句会传播到 Python,被try/except捕获。
19.5 模块导入
Section titled “19.5 模块导入”使用 py::module_::import() 导入 Python 模块。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// 基础模块导入py::object import_os_module() { // 等价于 Python 的 import os return py::module_::import("os");}
// 导入子模块py::object import_json_module() { // import json return py::module_::import("json");}
// 导入模块并调用函数py::object call_json_dumps() { py::module_ json = py::module_::import("json"); py::object dumps = json.attr("dumps");
// 构建 Python 对象作为参数 py::dict data = py::dict("name"_a = "test", "values"_a = py::list(py::make_tuple(1, 2, 3))); return dumps(data, py::arg("indent") = 2);}
// 访问标准库函数py::object use_collections() { py::module_ collections = py::module_::import("collections"); py::object Counter = collections.attr("Counter");
py::list words = py::list(py::make_tuple("apple", "banana", "apple", "orange", "banana")); return Counter(words);}
// 导入并检查模块属性py::object get_sys_info() { py::module_ sys = py::module_::import("sys");
// 获取属性 py::object version = sys.attr("version"); py::list path = py::cast<py::list>(sys.attr("path"));
return py::dict("version"_a = version, "path"_a = path);}
PYBIND11_MODULE(import_demo, m) { m.def("import_os", &import_os_module); m.def("import_json", &import_json_module); m.def("json_dumps", &call_json_dumps); m.def("word_count", &use_collections); m.def("sys_info", &get_sys_info);
m.def("call_repr", [](py::object obj) { // repr() 函数 py::object repr_func = py::module_::import("builtins").attr("repr"); return repr_func(obj); });
m.def("call_len", [](py::object obj) { // len() 函数 return py::len(obj); });}>>> import import_demo as m
>>> # 导入 os 模块>>> os_mod = m.import_os()>>> os_mod<module 'os'>
>>> # 导入 json 并使用>>> m.json_dumps()'{\n "name": "test",\n "values": [1, 2, 3]\n}'
>>> # 使用 collections.Counter>>> m.word_count()Counter({'apple': 2, 'banana': 2, 'orange': 1})
>>> # 获取 sys 信息>>> info = m.sys_info()>>> info['version'][:8]'3.12.0'>>> len(info['path'])> 0
>>> # 调用内置函数>>> m.call_repr("hello")"'hello'">>> m.call_len([1, 2, 3])3关键洞察:
py::module_::import("module_name")等价于 Python 的import module_name。导入后通过.attr()访问模块属性或函数。
19.6 执行Python代码字符串
Section titled “19.6 执行Python代码字符串”使用 py::exec 和 py::eval 执行代码字符串。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// py::eval:执行表达式,返回结果void eval_expressions() { // 计算表达式 py::object result = py::eval("2 + 3"); py::print("2 + 3 =", result);
// 多行表达式 result = py::eval("x = 5; y = 3; x + y"); py::print("x + y =", result);
// 使用全局和局部命名空间 py::dict globals = py::dict("a"_a = 10); py::dict locals = py::dict("b"_a = 20); result = py::eval("a + b", globals, locals); py::print("a + b =", result);}
// py::exec:执行语句,不关注返回值void exec_statements() { py::dict globals;
py::exec( "result = sum(range(10))", globals );
py::print("sum(range(10)) =", globals["result"]);
// 执行多条语句 py::exec( "import math\n" "x = math.sqrt(16)\n" "y = math.pow(2, 3)", globals ); py::print("sqrt(16) =", globals["x"]); py::print("pow(2, 3) =", globals["y"]);}
// py::eval_string 和 py::exec_stringvoid string_variants() { // 简写形式 py::object result = py::eval_string("10 * 10"); py::print("10 * 10 =", result);
// 带命名空间 py::dict globals = py::dict("msg"_a = "Hello"); py::dict locals; result = py::eval_string("msg + ' World'", globals, locals); py::print("eval result:", result);}
// 带错误处理的执行void exec_with_error_handling() { py::dict globals; try { py::exec("1/0", globals); } catch (const py::error_already_set& e) { py::print("Caught error:", e.what()); }}
// 动态构建并执行代码py::object dynamic_code_execution(const std::string& expression, py::dict context) { return py::eval(expression, context);}
PYBIND11_MODULE(exec_eval_demo, m) { m.def("eval_expr", [](const std::string& expr) { return py::eval(expr); });
m.def("exec_code", [](const std::string& code) { py::dict globals; py::exec(code, globals); return globals; });
m.def("dynamic_eval", &dynamic_code_execution);
m.def("safe_eval", [](const std::string& expr) { try { return py::eval(expr); } catch (const py::error_already_set& e) { return py::str("Error: ") + e.what(); } });
m.def("eval_with_globals", [](py::dict globals, const std::string& expr) { return py::eval(expr, globals); });}>>> import exec_eval_demo as m
>>> # 简单求值>>> m.eval_expr("2 + 3")5
>>> # 执行代码块>>> result = m.exec_code("x = 10\ny = 20\nz = x * y")>>> result['z']200
>>> # 带上下文的动态执行>>> context = {"a": 100, "b": 50}>>> m.dynamic_eval("a + b", context)150
>>> # 安全执行>>> m.safe_eval("valid expression")42>>> m.safe_eval("1/0")'Error: division by or zero'
>>> # 带全局变量的求值>>> globals_dict = {"multiplier": 3}>>> m.eval_with_globals(globals_dict, "multiplier * 7")21关键洞察:
py::eval()执行表达式并返回结果,py::exec()执行语句序列。两者都支持传入全局和局部命名空间字典,便于动态代码执行。py::eval_string/py::exec_string是接受 std::string 的简写变体。
调用 Python 代码总结:
| 功能 | pybind11 API | 说明 |
|---|---|---|
| 调用函数 | py::function(arg1, arg2) | 直接调用或 PyObject_Call |
| 构造参数 | py::tuple / py::dict | 位置参数和关键字参数 |
| 返回值处理 | py::cast<T>() | 显式类型转换 |
| 异常捕获 | py::error_already_set | 捕获 Python 异常 |
| 模块导入 | py::module_::import() | 等价 import 语句 |
| 执行代码 | py::eval() / py::exec() | 表达式或语句执行 |
实战建议:从 Python 调用 C++ 使用 py::function 参数传入。从 C++ 调用 Python 使用 py::module_::import() 导入模块。执行动态代码时注意异常处理,使用 py::error_already_set 捕获 Python 异常。