第18章 Python对象包装
pybind11 提供了完善的 Python 对象包装机制,通过 RAII 方式管理 Python 对象的生命周期,理解 py::object、py::handle 和引用计数规则是深入使用 pybind11 的基础。
18.1 py::object 基础
Section titled “18.1 py::object 基础”py::object 是 pybind11 中 Python 对象的 RAII 封装。
#include <pybind11/pybind11.h>#include <string>
namespace py = pybind11;
// py::object 是 PyObject* 的 RAII 封装// - 构造时增加引用计数// - 析构时减少引用计数// - 自动管理对象生命周期
void demonstrate_object() { // 创建 py::object(引用计数 +1) py::object str = py::str("Hello, py::object"); py::object num = py::int_(42); py::object list = py::list::reduce_exact(py::list()); // 空列表
// py::object 支持 Python 操作 py::print(str); py::print(num);
// 获取底层 PyObject* PyObject* raw = str.ptr();
// 判断对象类型 py::print("Is string?", py::isinstance<py::str>(str)); py::print("Is int?", py::isinstance<py::int_>(num));}>>> from object_basics import demonstrate_object>>> # 直接调用会触发内部输出>>> # Python 端无法直接调用 C++ void 函数,这里仅作演示关键洞察:
py::object是 pybind11 中管理 Python 对象的核心类型,它封装PyObject*并通过 RAII 自动管理引用计数。
18.2 py::cast 转换
Section titled “18.2 py::cast 转换”显式与隐式类型转换。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// C++ 类型到 Python 对象(隐式转换)void implicit_conversions() { int int_val = 42; double double_val = 3.14; std::string str_val = "hello";
// pybind11 自动将 C++ 类型转为 py::object py::object obj1 = int_val; // 隐式:py::cast(42) py::object obj2 = double_val; // 隐式:py::cast(3.14) py::object obj3 = str_val; // 隐式:py::cast("hello")}
// 显式转换(推荐,明确表达转换意图)void explicit_conversions() { int int_val = 42; double double_val = 3.14; std::string str_val = "world";
// 显式调用 py::cast(推荐) py::object obj1 = py::cast(int_val); py::object obj2 = py::cast(double_val); py::object obj3 = py::cast(str_val);
// 验证转换结果 py::print(obj1); py::print(obj2); py::print(obj3);}
// Python 对象转 C++ 类型void python_to_cpp() { // 从 Python 对象提取 C++ 值 py::object py_int = py::int_(100); py::object py_str = py::str("test");
// 显式 py::cast 指定目标类型 int i = py::cast<int>(py_int); std::string s = py::cast<std::string>(py_str);
py::print("int:", i); py::print("string:", s);}
// 泛型转换(使用 py::object 作为 Python 值的容器)py::object generic_processing(py::object obj) { // py::object 可存储任意 Python 对象 if (py::isinstance<py::int_>(obj)) { int val = py::cast<int>(obj); return py::cast(val * 2); } else if (py::isinstance<py::str>(obj)) { std::string val = py::cast<std::string>(obj); return py::str(val + " - processed"); } return py::none();}
PYBIND11_MODULE(cast_demo, m) { m.def("generic_processing", &generic_processing, "Process Python object and return result");}>>> import cast_demo as m
>>> # 处理整数>>> m.generic_processing(5)10
>>> # 处理字符串>>> m.generic_processing("hello")'hello - processed'
>>> # 处理其他类型>>> m.generic_processing([1, 2, 3]) # 返回 None关键洞察:
py::cast<T>()是类型转换的核心函数。C++ 转 Python 时隐式或显式调用均可,Python 转 C++ 必须显式调用py::cast<T>()。
18.3 handle vs object 区别
Section titled “18.3 handle vs object 区别”py::handle 与 py::object 的核心区别在于是否拥有对象所有权。
#include <pybind11/pybind11.h>#include <string>
namespace py = pybind11;
void demonstrate_difference() { py::object owned_str = py::str("I own this reference"); // owned_str 拥有引用所有权,析构时会 Py_DECREF
// handle:非拥有型引用 // 不增加引用计数,不负责释放 py::handle handle_str = owned_str.ptr(); // 同一个 PyObject* 有两个引用: // - owned_str 拥有所有权 // - handle_str 仅是观察者
py::print("handle type:", py::type::of(handle_str).attr("__name__")); py::print("object type:", py::type::of(owned_str).attr("__name__"));
// 指向同一对象 py::print("Same object?", handle_str == owned_str.ptr());}
// handle 用于需要观察但不拥有对象的场景void handle_usage_pattern() { py::dict dict = py::dict("key"_a = "value");
// 获取 handle 而不增加引用计数 py::handle key = py::str("key").ptr();
// 检查键存在 bool has_key = PyDict_Contains(dict.ptr(), key) == 1; py::print("Has key?", has_key);
// handle 不影响引用计数,原 dict 析构时正常释放}
// object 拥有完整生命周期管理void object_ownership() { py::object list = py::list();
// 创建一个新的 Python list 并拥有所有权 py::object new_list = py::list(py::cast(10)); // [10]
// 所有对象操作后,list 和 new_list 析构时会正确释放}
PYBIND11_MODULE(handle_object_demo, m) { m.def("check_key", [](py::dict d, const std::string& key) { // 使用 handle 检查字典键 py::handle h = py::str(key).ptr(); return PyDict_Contains(d.ptr(), h) == 1; });
m.def("copy_object", [](py::object obj) { // 复制对象(增加引用计数) Py_INCREF(obj.ptr()); return py::object(obj.ptr(), py::return_value_policy::reference); });}>>> import handle_object_demo as m
>>> d = {"key": "value", "num": 123}
>>> m.check_key(d, "key")True>>> m.check_key(d, "missing")False
>>> obj = m.copy_object([1, 2, 3])>>> obj[1, 2, 3]关键洞察:
py::handle是非拥有型引用,仅观察 Python 对象,不管理生命周期。py::object是拥有型引用,在析构时调用Py_DECREF。在需要观察对象但不拥有时使用handle,需要管理生命周期时使用object。
18.4 Python对象生命周期
Section titled “18.4 Python对象生命周期”引用计数规则是 Python 内存管理的核心。
#include <pybind11/pybind11.h>#include <string>#include <vector>
namespace py = pybind11;
// 规则1:创建 py::object 时,底层 PyObject 引用计数 +1void rule_1_creation() { // 从 Python 传入的对象,引用计数由 Python 侧管理 // pybind11 会负责调用 Py_DECREF py::object obj = py::none(); // obj 构造时 Py_INCREF,析构时 Py_DECREF}
// 规则2:py::object 拷贝构造共享底层 PyObject*void rule_2_copy() { py::object original = py::str("shared"); py::object copy = original; // 共享同一个 PyObject*
// 两者指向同一对象,引用计数为 2 py::print("Same?", original.ptr() == copy.ptr());
// copy 析构时不释放对象(因为 original 还持有)}
// 规则3:从 raw PyObject* 创建 py::object 需要指定归还策略void rule_3_raw_pointer() { PyObject* raw = PyUnicode_FromString("raw string"); // 此时 raw 引用计数为 1(自己创建)
// 方式1:默认不增加引用,创建 py::object 后它会释放 py::object obj1(raw); // 会调用 Py_DECREF 释放
// 方式2:引用计数 +1,由 py::object 管理释放 Py_INCREF(raw); py::object obj2(raw, py::return_value_policy::reference); // obj2 析构时 Py_DECREF 释放}
// 规则4:函数参数传入的 py::object 不增加引用计数void rule_4_function_arg(py::object obj) { // obj 指向 Python 传入的对象,不增加引用计数 // 函数返回时 py::object 析构会调用 Py_DECREF py::print("Got:", obj);}
// 规则5:返回 py::object 时需要正确的归还策略py::object create_return_object() { py::object local = py::str("local"); // 默认 return_value_policy::take_ownership 会调用 Py_DECREF return local;}
PYBIND11_MODULE(lifecycle_demo, m) { m.def("process", &rule_4_function_arg);
m.def("create_string", []() { return py::str("created by C++"); });
m.def("get_none", []() { return py::none(); });
m.def("wrap_pyobject", [](PyObject* obj) { // 从 C API 获取的对象包装成 py::object // 调用方仍然拥有对象所有权 return py::object(obj, py::return_value_policy::reference); });}>>> import lifecycle_demo as m
>>> # 函数参数传入>>> m.process("hello")Got: hello
>>> # 返回新对象>>> s = m.create_string()>>> s'created by C++'
>>> # 返回 None>>> m.get_none()
>>> # 包装 PyObject>>> # 需要谨慎处理所有权关键洞察:Python 对象生命周期由引用计数管理。创建
py::object时增加计数,析构时减少。从PyObject*创建时必须明确是谁拥有对象所有权,避免内存泄漏或重复释放。
18.5 引用计数机制
Section titled “18.5 引用计数机制”Py_INCREF / Py_DECREF vs Py_XDECREF 的区别。
#include <pybind11/pybind11.h>#include <string>
namespace py = pybind11;
// Py_INCREF / Py_DECREF:引用计数增减void reference_count_operations() { PyObject* obj = PyUnicode_FromString("test"); // 新建对象引用计数为 1
Py_INCREF(obj); // 引用计数变为 2
Py_DECREF(obj); // 引用计数变为 1,仍有效
Py_DECREF(obj); // 引用计数变为 0,对象被释放}
// Py_XDECREF:空指针安全的引用计数减少void xdecref_safe() { PyObject* null_obj = nullptr;
// Py_DECREF(nullptr) 是未定义行为(可能崩溃) // Py_XDECREF(nullptr) 安全,什么都不做 Py_XDECREF(null_obj); // 安全
PyObject* valid_obj = PyUnicode_FromString("safe"); Py_XDECREF(valid_obj); // 正常释放}
// 典型错误:过早释放void common_mistake() { PyObject* obj = PyUnicode_FromString("error");
// 错误:将对象所有权交给 py::object 后又自己释放 py::object owned(obj); // py::object 析构时会 Py_DECREF
// 如果这里手动 Py_DECREF,会导致双重释放 // Py_DECREF(obj); // 危险!双重释放}
// 正确做法:明确所有权void correct_ownership() { // 情况1:C++ 创建对象,py::object 负责释放 PyObject* created = PyUnicode_FromString("owned by object"); py::object obj1(created); // 默认 take_ownership
// 情况2:已有引用计数所有权的对象 Py_INCREF(created); // 增加引用计数,交给 py::object py::object obj2(created, py::return_value_policy::reference);
// 情况3:borrowed reference(借来的引用) PyObject* borrowed = PyUnicode_FromString("borrowed"); py::object obj3(borrowed, py::return_value_policy::reference); // obj3 析构时会 Py_DECREF borrowed}
// pybind11 辅助函数void pybind11_helpers() { py::object obj = py::str("helper");
// py::reinterpret_borrow:借来的引用,构造时不增加计数 PyObject* raw = obj.ptr(); py::object from_raw = py::reinterpret_borrow<py::object>(raw);
// py::reinterpret_steal:偷来的引用,构造时增加计数 PyObject* stolen = PyUnicode_FromString("stolen"); py::object from_stolen = py::reinterpret_steal<py::object>(stolen);}
PYBIND11_MODULE(refcount_demo, m) { m.def("safe_operation", []() { PyObject* obj = PyUnicode_FromString("safe"); Py_XDECREF(obj); // 安全释放 return true; });}>>> import refcount_demo as m
>>> m.safe_operation()True关键洞察:
Py_DECREF遇到空指针是未定义行为,Py_XDECREF遇到空指针安全跳过。pybind11 中优先使用py::object管理生命周期,仅在直接操作PyObject*时需要手动调用增减引用计数函数。
18.6 全局解释器状态
Section titled “18.6 全局解释器状态”GIL(Global Interpreter Lock)与 pybind11 中的 GIL 管理。
#include <pybind11/pybind11.h>#include <string>#include <thread>#include <vector>
namespace py = pybind11;
// GIL 保证 Python 对象操作的线程安全// py::gil_scoped_acquire:临时释放 GIL 的 RAII 封装void with_gil_held() { py::gil_scoped_acquire gil; // 此时持有 GIL,可以安全操作 Python 对象
py::object result = py::str("result"); py::print("In GIL:", result); // 离开作用域自动恢复 GIL 状态}
// 多线程中调用 Python 需要 GILvoid thread_safe_python_call() { std::vector<std::string> items = {"a", "b", "c"};
std::vector<std::thread> threads; for (const auto& item : items) { threads.emplace_back([item]() { // 每个线程需要获取 GIL py::gil_scoped_acquire gil; py::print("Processing:", item); }); }
for (auto& t : threads) { t.join(); }}
// Python 可调用对象在 C++ 线程中的使用void call_python_from_threads() { py::object callable = py::eval("lambda x: x * 2");
std::vector<int> values = {1, 2, 3, 4, 5}; std::vector<std::thread> threads;
for (int v : values) { threads.emplace_back([v, &callable]() { py::gil_scoped_acquire gil; py::object result = callable(py::cast(v)); py::print("Result:", result); }); }
for (auto& t : threads) { t.join(); }}
// gil_scoped_release:临时释放 GILvoid release_gil_temporarily() { // 释放 GIL 使其他 Python 线程可以执行 py::gil_scoped_release release;
// 这里是纯 C++ 计算,不涉及 Python 对象 double result = 0.0; for (int i = 0; i < 1000000; ++i) { result += i * 0.001; }
// 离开作用域自动重新获取 GIL py::gil_scoped_acquire gil; py::print("Computation done:", result);}
PYBIND11_MODULE(gil_demo, m) { m.def("compute_heavy", &release_gil_temporarily, "Computation while GIL is released");
m.def("parallel_calls", []() { py::object func = py::eval("lambda x: x + 1"); std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) { threads.emplace_back([i, &func]() { py::gil_scoped_acquire gil; int result = py::cast<int>(func(py::cast(i))); }); }
for (auto& t : threads) { t.join(); } });}>>> import gil_demo as m
>>> m.compute_heavy()Computation done: 499999.5
>>> m.parallel_calls() # 并行调用,输出交错关键洞察:Python GIL 保证多线程环境下 Python 对象操作的原子性。pybind11 提供 RAII 方式的
py::gil_scoped_acquire和py::gil_scoped_release管理 GIL 状态,在 C++ 计算密集型任务中临时释放 GIL 可提高并行性。
Python 对象包装总结:
| 类型 | 特性 | 适用场景 |
|---|---|---|
py::object | RAII 拥有型引用,析构时 Py_DECREF | 标准 Python 对象封装 |
py::handle | 非拥有型观察者引用 | 检查对象但不拥有 |
py::cast<T>() | 显式类型转换 | Python/C++ 间类型转换 |
py::gil_scoped_acquire | RAII GIL 获取 | 多线程安全 Python 调用 |
py::gil_scoped_release | RAII GIL 释放 | C++ 密集计算时提升性能 |
实战建议:优先使用 py::object 管理对象生命周期,谨慎处理 PyObject*。多线程场景必须获取 GIL。计算密集型任务可用 py::gil_scoped_release 临时释放 GIL。