Skip to content

第18章 Python对象包装

pybind11 提供了完善的 Python 对象包装机制,通过 RAII 方式管理 Python 对象的生命周期,理解 py::object、py::handle 和引用计数规则是深入使用 pybind11 的基础。

py::object 是 pybind11 中 Python 对象的 RAII 封装。

object_basics.cpp
#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 自动管理引用计数。

显式与隐式类型转换。

cast_conversion.cpp
#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>()。

py::handle 与 py::object 的核心区别在于是否拥有对象所有权。

handle_vs_object.cpp
#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。

引用计数规则是 Python 内存管理的核心。

lifecycle.cpp
#include <pybind11/pybind11.h>
#include <string>
#include <vector>
namespace py = pybind11;
// 规则1:创建 py::object 时,底层 PyObject 引用计数 +1
void 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* 创建时必须明确是谁拥有对象所有权,避免内存泄漏或重复释放。

Py_INCREF / Py_DECREF vs Py_XDECREF 的区别。

refcount.cpp
#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* 时需要手动调用增减引用计数函数。

GIL(Global Interpreter Lock)与 pybind11 中的 GIL 管理。

gil_state.cpp
#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 需要 GIL
void 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:临时释放 GIL
void 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::objectRAII 拥有型引用,析构时 Py_DECREF标准 Python 对象封装
py::handle非拥有型观察者引用检查对象但不拥有
py::cast<T>()显式类型转换Python/C++ 间类型转换
py::gil_scoped_acquireRAII GIL 获取多线程安全 Python 调用
py::gil_scoped_releaseRAII GIL 释放C++ 密集计算时提升性能

实战建议:优先使用 py::object 管理对象生命周期,谨慎处理 PyObject*。多线程场景必须获取 GIL。计算密集型任务可用 py::gil_scoped_release 临时释放 GIL。