ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

pybind11 升级指南:从 v2.0 到 v3.0 的迁移路线、破坏性变更与实战要点

pybind11 升级指南:从 v2.0 到 v3.0 的迁移路线、破坏性变更与实战要点 pybind11 升级指南从 v2.0 到 v3.0 的迁移路线、破坏性变更与实战要点【免费下载链接】pybind11Seamless operability between C11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11本文是 pybind11 官方 docs/upgrade.rst 升级指南的深度解读它配套 changelog 使用changelog 罗列新特性、改进与修复的完整清单而升级指南只聚焦真正影响你升级体验的那部分——被弃用的 API 及其替代方案、构建系统变更、代码现代化建议等。读完本文你将掌握从 pybind11 v2.0 一路升级到 v3.0 需要知道的全部破坏性变更、推荐迁移路径包括py::smart_holder、py::native_enum等新特性的采用时机并能用预处理器条件编译兼容新旧两代版本。升级指南在 pybind11 文档体系中的定位升级指南是 changelog 的“伴侣文档”。它的价值在于changelog 告诉你“加了什么”升级指南告诉你“你升级后需要改什么”。指南中的每一个条目都对应真实的源码变更例如文档中提到的特性宏可以在 include/pybind11/detail/common.h 中找到定义#define PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT 1 #define PYBIND11_HAS_NATIVE_ENUM 1这两个宏正是升级指南 v3.0 部分提到的两个预处理器守卫PYBIND11_HAS_NATIVE_ENUM与PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT它们由新版本头文件自动定义用于条件编译兼容代码。最新版的完整弃用清单可查阅 docs/advanced/deprecated.rst。升级到 v3.0主要新特性与迁移要点pybind11 v3.0 引入了重大新特性但绝大多数现有扩展无需修改即可构建运行。只有极少数情况需要小幅调整且这些调整可以很容易地用预处理器条件编译包裹起来以保持与 2.x 系列的兼容。ABI 兼容性与全量重编译建议扩展模块的 ABI 不兼容是 v3.0 升级中最需要留意的一点由于新特性与现代化改动用 pybind11 v3.0 构建的扩展与用 v2.13 构建的扩展之间不保持 ABI 兼容。为保证跨扩展模块兼容官方建议用 v3.0重新构建所有基于 pybind11 的扩展。跨扩展模块 ABI 兼容性的处理在 v3.0 中经历了一次重大现代化新实现能比旧版本更精确地反映真实的 ABI 兼容程度但细节微妙而复杂。CMake切换到现代 FindPython 模块v3.0 的 CMake 支持默认采用现代FindPython模块。如果你还没更新pybind11 对旧的PYTHON_*变量提供了部分向后兼容但你应该切换到使用Python_*变量。注意设置PYTHON_*变量不再影响构建。实际的兼容逻辑在 tools/pybind11Common.cmake 中当PYBIND11_FINDPYTHON未定义、等于COMPAT或为真时pybind11 才会走新的FindPython路径COMPAT模式会打印提示信息并把Python_*变量映射回PYTHON_*以保持兼容。推荐做法是显式设置set(PYBIND11_FINDPYTHON ON)这个选项已被支持多年设置后可以避免进入兼容模式也就避免了兼容模式警告。py::smart_holder 与 py::classh智能指针持有者的现代化v3.0 的一大新特性是集成了py::smart_holder它改善了对std::unique_ptr和std::shared_ptr的支持解决了一系列长期存在的问题详见 docs/advanced/classes.rst 中的 smart holder 章节。与之紧密相关的是新增的py::trampoline_self_life_support详见 docs/advanced/classes.rst 中 virtual 覆盖章节头文件为 include/pybind11/trampoline_self_life_support.h。为了便于快速尝试py::smart_holderpybind11 提供了py::classh快捷键。其定义位于 include/pybind11/pybind11.h// py::classhPet 是 py::class_Pet, py::smart_holder 的简写 using classh class_type_, smart_holder, options...;例如py::classhPet(m, Pet) // 等价于 py::class_Pet, py::smart_holder(m, Pet)py::classh的设计意图是让你在不引入大量空白差异whitespace changes的前提下轻松试验py::smart_holder。在很多情况下把代码里的py::class_全局替换为py::classh是一个有效的第一步构建失败会迅速暴露出需要移除std::shared_ptr...holder 的位置运行期失败假设有良好的单元测试覆盖会突出需要协同修改的基类-派生类场景。注意 include/pybind11/stl_bind.h 中的py::bind_vector与py::bind_map有一个holder_type模板参数默认是std::unique_ptr。如果需要py::smart_holder的功能请显式指定例如py::bind_vectorVecType, py::smart_holder(m, VecType);py::native_enum现代枚举绑定 APIv3.0 新增py::native_enum头文件 include/pybind11/native_enum.h用于把 C 枚举暴露为 Python 原生类型——通常是标准库的enum.Enum或其子类。相比旧的现已弃用的py::enum_它与 Python 的枚举体系集成得更好。两个重要注意点必须显式引入头文件#include pybind11/native_enum.h不会被自动包含弃用声明2.x 系列中产生弃用警告的任何内容都可能在 3.x 的未来小版本中被移除大部分在 3.0 中仍然保留以缓解过渡。绑定函数现在支持 pickle使用 pybind11 暴露的函数现在可被 pickle这移除了一个长期存在的障碍——依赖 pickle 的 Python 特性如 multiprocessing、缓存工具之前无法直接使用 pybind11 绑定的函数。自定义 type caster 的模板特化需求潜在障碍以下问题极不可能出现但一旦出现也很容易绕开场景一C 枚举通过自定义 type caster 绑定到 Python。如果自定义 type caster 是模板化的可能需要如下模板特化#if defined(PYBIND11_HAS_NATIVE_ENUM) namespace pybind11::detail { template typename FancyEnum struct type_caster_enum_type_enabled FancyEnum, enable_if_tis_fancy_enumFancyEnum::value : std::false_type {}; } #endifPYBIND11_HAS_NATIVE_ENUM守卫仅在需要向后兼容 pybind11 v2 时才需要。场景二自定义了pybind11::detail::copyable_holder_caster或pybind11::detail::move_only_holder_caster实现且用于std::shared_ptr/std::unique_ptr转换注意这两个 caster 从未被正式文档化虽然自 2017 年起就存在。此时可能需要#if defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT) namespace pybind11::detail { template typename ExampleType struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled ExampleType, enable_if_tis_example_typeExampleType::value : std::false_type {}; } #endif#if defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT) namespace pybind11::detail { template typename ExampleType struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled ExampleType, enable_if_tis_example_typeExampleType::value : std::false_type {}; } #endifPYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT守卫仅在需要向后兼容 pybind11 v2 时才需要。官方迁移建议官方建议尽快迁移到 v3.0同时把初始改动保持在最小。大多数项目只需更新 pybind11 版本即可升级无需改动既有绑定代码。经过一段短暂的稳定期足以暴露任何细微问题后再按需增量采用新特性按需使用py::smart_holder与py::trampoline_self_life_support改善代码健康度py::classh是快速试验的捷径逐步从py::enum_迁移到py::native_enum改善与 Python 标准枚举类型的集成没有紧迫性去重构已经正常工作的绑定——按需或随维护工作采用新特性即可如果使用 CMake更新为Python_*变量并尽量设置set(PYBIND11_FINDPYTHON ON)。旧版本升级要点汇总v2.12 ~ v2.0v2.12NumPy 2.x 支持NumPy 支持已升级到 2.x 系列两个相关变化dtype.flags()现在是uint64dtype.alignment()是ssize_tNumPy 2.x 中itemsize()可能返回超出整数范围的值长期弃用的PyArray_GetArrayParamsFromObject不再可用。更直接的改变是默认整型int_以及uint现在是ssize_t而不是long影响 64 位 Windows。如需暂时只支持 NumPy 1.x可定义PYBIND11_NUMPY_1_ONLY禁用新支持——但必须在所有 pybind11 编译单元上一致地定义否则可能导致 ODR 违规。该选项未来会被移除强烈建议尽快适配代码。需要特别提醒在本仓库当前版本中PYBIND11_NUMPY_1_ONLY已经不再支持——include/pybind11/numpy.h 中定义了该宏时会直接报编译错误参见 PR #5595。因此旧版升级指南中的这个逃生舱口在新版本中已失效迁移到 NumPy 2.x 是唯一路径。v2.11CMake 最低版本要求最低 CMake 版本提升到 3.5。注意 CMake 3.27 移除了长期弃用的FindPythonInterp支持如果你把 3.27 设为最小或最大支持版本。为未来做准备强烈推荐 CMake 3.15 配合FindPython或设置PYBIND11_FINDPYTHON否则 pybind11 会在FindPythonInterp不可用时自动切换到FindPython。这正是后续 v3.0 默认行为的铺垫。v2.9命名空间与 caster 名称py::make_simple_namespace的用法应改为py::module_::import(types).attr(SimpleNamespace)自定义 type caster 中的_可用更可读的const_name替代旧_快捷键保留除非用作宏如 gettext。v2.7py::str 的严格化v2.7 之前py::str可以持有PyUnicodeObject或PyBytesObjectpy::isinstancestr()对两者都返回true。从 v2.7 起py::str只持有PyUnicodeObjectpy::isinstancestr()只对py::str为true。PYBIND11_STR_LEGACY_PERMISSIVE宏作为逃生舱口可恢复旧行为在 include/pybind11/detail/common.h 中有注释说明未来会被移除。两类常见修复被旧行为掩盖的py::str/py::bytes混用——把py::str改成py::bytes即可依赖py::isinstancestr(obj)对py::bytes为真——多数情况加|| py::isinstancebytes(obj)即可若出现在模板中则需仔细审查并定制修复。v2.6命名规范与行为收紧宏更名PYBIND11_OVERLOAD*和get_overload应替换为PYBIND11_OVERRIDE*和get_overridedocs/advanced/classes.rst 中明确说明更名发生在 v2.5.0 左右旧名称未来可能被移除模块类型更名py::module更名为py::module_保留向后兼容 typedef。原因是 C20 语言规则要求未限定的module不能出现在逻辑行行首构造函数弃用py::module_的公开构造函数被弃用改用PYBIND11_MODULE或module_::create_extension_module行为收紧子类忘记调用__init__现在会抛错向子类做非法转换如从py::object转py::bytes现在抛py::type_errorAPI 调整未文档化的h.get_type()弃用改用py::type::of(h)枚举预定义__str__要覆盖时在定义__str__处加py::prepend()标签定义__eq__而未定义__hash__时__hash__会被置为None与 CPython 一致需要哈希则用py::hash快捷键py::array构造函数尺寸统一为有符号整数可能引发编译警告请转为py::ssize_t工具迁移tools/clang子模块和tools/mkdoc.py迁移到独立的 pybind11-mkdoc 包wheel 头文件槽位PyPI 上的 pybind11 包不再填充 wheel 的 headers 槽位需要时可pip install pybind11[global]。多数用户不受影响因为python -m pybind11 --includes与pybind11.get_include()自 2.5 起一直正确指向pybind11/include。v2.6 的 CMake 变更重要PYBIND11_CPP_STANDARD平台标志弃用改用CMAKE_CXX_STANDARD数字或target_compile_features未显式要求标准时pybind11 目标使用编译器默认标准不低于 C11不再强制 C14。依赖旧行为的请用set(CMAKE_CXX_STANDARD 14 CACHE STRING )pybind11::module的直接使用应配合set(CMAKE_CXX_VISIBILITY_PRESET hidden)或类似设置pybind11_add_module的SYSTEM参数弃用且无效果链接行为与其它导入库一致默认按SYSTEM库处理未设置PYTHON_EXECUTABLE时虚拟环境venv、virtualenv、conda优先于标准搜索CMAKE_INTERPROCEDURAL_OPTIMIZATION若已设置会被pybind11_add_module尊重替代链接pybind11::lto/pybind11::thin_lto在 pybind11 之前使用find_package(Python COMPONENTS Interpreter Development)会让 pybind11 使用新的 Python 机制而非自定义搜索未来可能成为默认。v2.5头文件随 Python 包分发Python 包现在把头文件作为数据包含在包自身中同时也放在 headers wheel 槽位。pybind11 --includes与pybind11.get_include()报告新位置无论安装方式如何都始终正确旧user参数失去意义。v2.2模块入口宏与符号可见性PYBIND11_PLUGIN宏弃用PYBIND11_MODULE成为首选// old PYBIND11_PLUGIN(example) { py::module m(example, documentation string); m.def(add, [](int a, int b) { return a b; }); return m.ptr(); } // new PYBIND11_MODULE(example, m) { m.doc() documentation string; // optional m.def(add, [](int a, int b) { return a b; }); }自定义构造函数与 pickle 的新 API旧的 placement-new 自定义构造函数弃用新方式用py::init()与工厂函数显著提升类型安全placement-new 可能意外用不兼容类型调用或在不谨慎的 Python 侧__init__调用下重复初始化同一对象。详见 docs/advanced/classes.rst 的自定义构造器与 pickling 章节// old -- deprecated (runtime warning shown only in debug mode) py::classFoo(m, Foo) .def(__init__, [](Foo self, ...) { new (self) Foo(...); // uses placement-new }); // new py::classFoo(m, Foo) .def(py::init([](...) { // Note: no self argument return new Foo(...); // return by raw pointer // or: return std::make_uniqueFoo(...); // return by holder // or: return Foo(...); // return by value (move constructor) }));pickle 同理py::pickle()成为首选// old -- deprecated (runtime warning shown only in debug mode) py::classFoo(m, Foo) ... .def(__getstate__, [](const Foo self) { return py::make_tuple(self.value1(), self.value2(), ...); }) .def(__setstate__, [](Foo self, py::tuple t) { new (self) Foo(t[0].caststd::string(), ...); }); // new py::classFoo(m, Foo) ... .def(py::pickle( [](const Foo self) { // __getstate__ return py::make_tuple(self.value1(), self.value2(), ...); // unchanged }, [](py::tuple t) { // __setstate__, note: no self argument return new Foo(t[0].caststd::string(), ...); // or: return std::make_uniqueFoo(...); // return by holder // or: return Foo(...); // return by value (move constructor) } ));构造与 pickle 的警告在模块初始化时import 时而非函数调用时显示且只在 debug 模式下可见。示例警告pybind11-bound class mymodule.Foo is using an old-style placement-new __init__ which has been deprecated. See the upgrade guide in pybind11s docs.符号隐藏的严格化pybind11 从 v2.2 起更严格地强制模块隐藏符号一是声明pybind11命名空间内所有符号为隐藏二是在 Linux/macOS 上自动附带-fvisibilityhidden标志仅针对扩展模块不影响内嵌解释器。这样确保不同 pybind11 版本编译的模块互不冲突py::module_local绑定等新特性按预期工作。在 CMake 构建系统中pybind11_add_module以前只在 release 模式设置该标志现在无条件应用且不能用NO_EXTRAS取消pybind11::module目标也把该标志加进接口pybind11::embed不变。如果你的 Python 模块同时充当共享库有依赖者需要手动导出符号或把共享库拆出来。临时恢复默认可见性的方法不推荐长期使用target_link_libraries(mymodule PRIVATE pybind11::module) add_library(restore_default_visibility INTERFACE) target_compile_options(restore_default_visibility INTERFACE -fvisibilitydefault) target_link_libraries(mymodule PRIVATE restore_default_visibility)本地 STL 容器绑定旧版只能全局绑定类型——所有模块共享同一导出类型两个模块导出相同 C 类型尤其是std::vectorint这类常见类型会冲突。py::module_local用于解决此问题完整用法见 docs/advanced/classes.rst 的 module_local 章节STL 绑定细节见 docs/advanced/cast/stl.rst。py::class_仍默认全局绑定但py::bind_vector和py::bind_map在元素为内置类型、未用py::class_绑定或绑定为py::module_local时会把 STL 容器绑定为py::module_local——这让多个模块可各自绑定std::vectorint而不冲突。升级时注意模块间 C→Python 方向的转换会受本地化影响Python→C 方向仍可接受外来py::module_local类型。若多个模块需要共享单个全局 STL 绑定要么在所有需要的模块中各加一份相同的 STL 绑定要么用py::module_local(false)恢复该绑定的全局状态。负步幅支持负步幅要求py::buffer_info与py::array接口的整型从无符号改为有符号。启用编译警告后可能看到新的转换警告用static_cast消除即可。部分 py::object API 弃用指针比较用obj1.is(obj2)等价于 Python 的obj1 is obj2旧operator弃用borrowed/stolen构造标签改为直接使用borrowed_t{}/stolen_t{}。编译期错误检查更严格std::shared_ptrT的自动转换在T未直接注册到py::class_T时不可行如std::shared_ptrint不能自动转换绑定这类参数现在直接编译报错。py::init...()也更严格阻止可能引发意外行为的绑定struct Example { Example(int ); }; py::class_Example(m, Example) .def(py::initint ()); // OK, exact match // .def(py::initint()); // compile-time error, mismatch非const左值引用不能绑定右值但const T 构造函数仍可用py::initT()注册因为const左值引用可以绑定右值。v2.1编译器版本与静态属性最低编译器版本在编译期强制检查v2.0 已有要求v2.1 起显式报错GCC 4.8、clang 3.3appleclang 5.0、MSVC 2015u3、Intel C 15.0。静态属性不再需要 py::metaclass绑定类默认支持静态属性零参数的py::metaclass()弃用新增一参数py::metaclass(python_type)用于少数需要自定义元类覆盖 pybind11 默认值的场景// old -- emits a deprecation warning py::class_Foo(m, Foo, py::metaclass()) .def_property_readonly_static(foo, ...); // new -- static properties work without the attribute py::class_Foo(m, Foo) .def_property_readonly_static(foo, ...); // new -- advanced feature, override pybind11s default metaclass py::class_Bar(m, Bar, py::metaclass(custom_python_type)) ...v2.0py::class_ 的破坏性变更v2.0 的变更是为了支撑 PyPy 的 cpyext 机制、提升效率以及让类型定义面向未来buffer protocol 必须显式声明提供 buffer 协议访问的类型现在必须在py::class_构造参数中带py::buffer_protocol()py::class_Matrix(Matrix, py::buffer_protocol()) .def(py::init...()) .def_buffer(...);静态属性曾需要 py::metaclass()此要求在 v2.1 已移除。若从 1.x 升级建议直接跳到 v2.1 或更新版本。trampoline 语法变化v1.x 的.aliasMyClass()改为在py::class_模板中同时指定原类与 trampoline 类// old v1.x syntax py::class_TrampolineClass(MyClass) .aliasMyClass() ... // new v2.x syntax py::class_MyClass, TrampolineClass(MyClass) ...原类必须是py::class_的第一个模板参数trampoline 可与其他参数基类、holder任意顺序混合。新方案在 Python 不覆盖任何 C 函数时零开销。py::baseT() 弃用改为把基类作为py::class_模板参数天然支持多重继承// old v1.x py::class_Derived(Derived, py::baseBase()); // new v2.x py::class_Derived, Base(Derived); // new -- multiple inheritance py::class_Derived, Base1, Base2(Derived); // new -- apart from Derived the argument order can be arbitrary py::class_Derived, Base1, Holder, Base2, Trampoline(Derived);std::shared_ptr 开箱即用相关 type caster 已内置不再需要PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptrT)保留该声明也不会报错或警告但完全冗余。py::object API 弃用对照表旧写法均会产生弃用警告旧语法新语法obj.call(args...)obj(args...)obj.str()py::str(obj)auto l py::list(obj); l.check()py::isinstancepy::list(obj)py::object(ptr, true)py::reinterpret_borrowpy::object(ptr)py::object(ptr, false)py::reinterpret_stealpy::object(ptr)if (obj.attr(foo))if (py::hasattr(obj, foo))if (obj[bar])if (obj.contains(bar))实战迁移清单综合各版本要点一份可操作的升级检查清单如下先升级版本、暂不动代码更新 pybind11 版本后构建用编译错误清单驱动修改CMake 层PYTHON_*→Python_*设置set(PYBIND11_FINDPYTHON ON)确保 C 标准通过CMAKE_CXX_STANDARD指定全量重编译v3.0 与 v2.13 不 ABI 兼容所有扩展模块需用 v3.0 重建实验性采用新特性全局替换py::class_→py::classh快速试探smart_holderpy::enum_→py::native_enum记得显式#include pybind11/native_enum.h处理自定义 caster 特化如需兼容 v2用PYBIND11_HAS_NATIVE_ENUM与PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT守卫包裹特化关注宏名与 API 更名PYBIND11_OVERLOAD*→PYBIND11_OVERRIDE*、py::module→py::module_、get_type()→py::type::of()清理旧式构造/pickleplacement-new 的__init__/__getstate__/__setstate__迁移到py::init工厂与py::pickle()符号可见性确认构建系统已应用-fvisibilityhidden模块若同时是共享库需显式导出符号。本文所有结论均以当前仓库中的 docs/upgrade.rst、include/pybind11/detail/common.h、include/pybind11/pybind11.h、include/pybind11/native_enum.h、include/pybind11/numpy.h、tools/pybind11Common.cmake 及 docs/changelog.md 为事实依据。若要查看这些变更在真实项目中的验证方式可参考仓库 tests 目录下对应的测试用例例如 tests/test_native_enum.cpp 与 tests/test_class_sh_basic.cpp它们分别覆盖了py::native_enum与smart_holder的运行时行为。【免费下载链接】pybind11Seamless operability between C11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表