Skip to content

第19章 调用Python代码

pybind11 允许在 C++ 代码中调用 Python 函数、导入模块、执行代码字符串,实现 C++ 与 Python 间的双向交互。

通过 py::function 调用 Python 可调用对象。

calling_python.cpp
#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 调用 lambda
py::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) 传递参数。

构建 Python 函数调用参数。

parameter_construction.cpp
#include <pybind11/pybind11.h>
#include <string>
#include <vector>
namespace py = pybind11;
// 方式1:直接传递 py::object
void 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::dict
void 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 支持混合传参。

处理 Python 函数返回值。

return_handling.cpp
#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 容器长度。

捕获和处理 Python 抛出的异常。

exception_handling.cpp
#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: TypeError
Exception value: type mismatch

关键洞察:py::error_already_set 是 Python 异常的包装。捕获后可通过 .what() 获取信息,使用 .restore() 恢复异常状态。throw 语句会传播到 Python,被 try/except 捕获。

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

module_import.cpp
#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() 访问模块属性或函数。

使用 py::exec 和 py::eval 执行代码字符串。

exec_eval.cpp
#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_string
void 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 异常。