ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

如何把 pybind11 类绑定从 std::shared_ptr 持有切换到 py::smart_holder?

如何把 pybind11 类绑定从 std::shared_ptr 持有切换到 py::smart_holder? 如何把 pybind11 类绑定从 std::shared_ptr 持有切换到 py::smart_holder【免费下载链接】pybind11Seamless operability between C11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11如果你的 pybind11 扩展目前用py::class_T, std::shared_ptrT这类写法管理 C 对象生命周期并且你计划升级到 pybind11 v3或已经在 v3 上那么官方的建议是把这些绑定切换到内置的py::smart_holder持有类型。v3.0 的升级指南把py::smart_holder列为本版本的核心新特性并明确给出迁移方法它可以同时支持std::unique_ptrT和std::shared_ptrT的双向转换解决了旧持有类型长期存在的若干问题。本文按照 docs/upgrade.rst 和 docs/advanced/smart_ptrs.rst 的说明给出从std::shared_ptr持有切换到py::smart_holder的完整操作路径怎么改、改哪里会报错、改完如何验证。先确认前提这是 pybind11 v3 的功能py::smart_holder从 pybind11 v3 开始内置是大多数场景下的推荐持有类型但出于向后兼容它不是默认持有类型官方也没有计划让它成为默认见 docs/advanced/smart_ptrs.rst。v3.0 构建的扩展模块与 v2.13 构建的模块不 ABI 兼容。升级后建议用 v3.0 重新构建所有基于 pybind11 的扩展以保证跨扩展模块的兼容性见 docs/upgrade.rst。不打算升级 v3 的项目无需本次迁移2.x 上继续使用std::shared_ptr持有仍然有效。为什么要切std::shared_ptr 持有的两个已知缺陷在动手前了解切换的动因有助于判断哪些绑定值得切。docs/advanced/smart_ptrs.rst 指出std::shared_ptr持有相对py::smart_holder有两个明显缺点一个 C 类型T的py::class_包括它的所有派生类型只能使用单一持有类型。如果用了std::shared_ptrT持有std::unique_ptrT连从 C 传给 Python 都不行而且这种问题往往只在运行时暴露常见表现是 segmentation fault。与默认std::unique_ptr持有一样基类/派生类之间的处理涉及严格意义上未定义行为的reinterpret_cast只是在多数情况下表现正常。切换到py::smart_holder后你同时获得std::unique_ptrT与std::shared_ptrT的双向转换把 Python 对象经std::unique_ptrT传回 C 时的安全 disown脱离 Python 所有权trampoline 对象Python 中重写 C 虚函数的对象经智能指针传回 C 时关联 Python 对象自动保活以及对std::enable_shared_from_this的完整支持。迁移步骤1. 把py::class_的持有类型改为py::smart_holder直接改法是给模板参数加上py::smart_holder// 迁移前std::shared_ptr 持有 py::class_Example, std::shared_ptrExample /* - holder type */(m, Example); // 迁移后smart_holder py::class_Example, py::smart_holder(m, Example);如果有继承链基类和派生类必须一起改因为整个类型链只能共用一个持有类型。例如带虚函数重写trampoline的Animal/Dog例子迁移后的绑定代码是PYBIND11_MODULE(example, m, py::mod_gil_not_used()) { py::class_Animal, PyAnimal /* trampoline */, py::smart_holder(m, Animal) .def(py::init()) .def(go, Animal::go); py::class_Dog, Animal, py::smart_holder(m, Dog) .def(py::init()); m.def(call_go, call_go); }同时注意trampoline 辅助类需要继承py::trampoline_self_life_supportpybind11 会在编译期强制这一点——trampoline 与py::smart_holder组合时必须继承该基类而把它和非py::smart_holder的持有类型组合会导致编译失败见 docs/advanced/classes.rst。2. 可选捷径全局替换为py::classh升级指南给出的实用技巧是py::classhPet是py::class_Pet, py::smart_holder的简写h即 smart_holder且与py::class_等长便于在 diff 中做实验。对很多代码库把py::class_全局替换成py::classh是有效的第一步构建失败会很快指出哪些地方还需要移除std::shared_ptr...持有类型在有良好单元测试覆盖的前提下运行时失败会指出哪些基类/派生类场景需要协调修改。也就是说验证手段之一就是构建 单测本身编译错误定位残留的旧持有类型单测暴露继承链上没改全的类型。3. 有虚拟函数重写时补齐 trampoline 要求如果迁移涉及 Python 侧继承并重写 C 虚函数trampoline 类要写成Animal基类带py::trampoline_self_life_supportclass PyAnimal : public Animal, public py::trampoline_self_life_support { public: using Animal::Animal; // Inherit constructors std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ go, /* Name of function in C (must match Python name) */ n_times /* Argument(s) */ ); } };py::trampoline_self_life_support的作用是确保std::unique_ptr能在 Python 与 C 之间安全传递。文档提醒在决定不使用py::smart_holder之前要三思回避它带来的坑是真实存在的而使用它的开销很可能在噪声级别。4. 检查 STL 容器绑定可选分支如果你用pybind11/stl_bind.h里的py::bind_vector/py::bind_map它们有一个默认值为std::unique_ptr的holder_type模板参数。若希望或需要py::smart_holder功能要显式指定例如py::bind_vectorVecType, py::smart_holder见 docs/upgrade.rst。5. 处理自定义 holder caster 特化少数情况升级指南列出了 v3.0 迁移中非常少见且容易绕过的障碍如果你的代码里有针对std::shared_ptr/std::unique_ptr转换的自定义pybind11::detail::copyable_holder_caster或move_only_holder_caster特化可能需要加类似的模板特化来关闭 smart_holder 支持下的旧路径且仅在需要兼容 pybind11 v2 时用PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT宏做条件编译。没有这类自定义特化的代码库可以跳过本节。验证结果按文档给出的方式验证迁移是否完成构建全部模块用 v3.0 重新构建通过。若用了py::classh全局替换构建错误会直接指出残留的std::shared_ptr...持有位置。单元测试跑现有测试覆盖基类/派生类场景这是文档指出的发现需要协调修改的运行时问题的手段。功能行为文档示例输出用Animal/Dog例子Python 侧派生类重写虚函数后调用应返回重写结果 from example import * d Dog() call_go(d) woof! woof! woof! class Cat(Animal): ... def go(self, n_times): ... return meow! * n_times ... c Cat() call_go(c) meow! meow! meow! disown 语义若函数接收std::unique_ptrT参数py::smart_holder下把 Python 对象经std::unique_ptr传回 C 是安全的 disown——传过一次后原 Python 对象已被移交再使用会抛出ValueError: Missing value for wrapped C type ... Python instance was disowned.当对象的use_count ! 1时则抛出ValueError: Cannot disown use_count ! 1 (load_as_unique_ptr).。仓库中的 tests/test_class_sh_basic.py 完整展示了这些断言如test_pass_unique_ptr_disowns、test_cannot_disown_use_count_ne_1可作为你自己测试的参照。行为差异与限制迁移后有两处行为差异需要知晓见 docs/advanced/classes.rst 的 Avoiding Inheritance Slicing and std::weak_ptr surprises 一节shared_ptr 转换语义变了旧std::shared_ptr持有下obj.caststd::shared_ptrT()得到的是与原 class_ 持有共享所有权的shared_ptr对 trampoline 派生的 Python 类可能出现 inheritance slicingPython 对象销毁后只剩基类 C 对象存活。py::smart_holder下转换得到的是独立控制块的新shared_ptr会保持派生 Python 对象存活避免了 slicing但构造std::weak_ptr时可能出现意外行为。需要与 smart_holder 共享控制块的 weak_ptr 时可以用py::potentially_slicing_weak_ptrT(obj)显式获取——代价是重新引入潜在的 inheritance slicing。如果精确管理派生 Python 对象生命周期很重要文档建议用 Python 侧的weakref它同时避开 slicing 和 C 侧std::weak_ptr语义的干扰。同一个 C 类型链只能有一个持有类型这条约束在迁移前后都成立是基类/派生类必须一起改的原因。完成上述步骤后你的绑定就运行在 v3.0 推荐的持有机制上后续如需进一步采用py::native_enum等其他 v3.0 新特性可继续参考 docs/upgrade.rst 的 Migration Recommendations 部分。【免费下载链接】pybind11Seamless operability between C11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表