恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
【Bug已解决】Test failures under Python 3.14 on x86_64-linux 解决方案
首页
资讯中心
/
【Bug已解决】Test failures under Python 3.14 on x86_64-linux 解决方案
【Bug已解决】Test failures under Python 3.14 on x86_64-linux 解决方案
发布时间:2026/8/2 7:20:25
【Bug已解决】Test failures under Python 3.14 on x86_64-linux 解决方案一、现象长什么样项目在 CI 矩阵里加入了 Python 3.14x86_64-linux结果一批测试挂了而 Python 3.10~3.13 全部通过FAILED tests/test_types.py::test_union_introspection FAILED tests/test_hints.py::test_get_origin ... Python 3.14 专属失败3.13 及以下全绿典型报错AssertionError assert types.UnionType is typing.Union # 或 TypeError unsupported operand type(s) for |: ...最小判据触发在 Python 3.14 上跑测试 现象类型 introspection / 语法相关测试失败低版本正常 根因代码对 PEP 604 的 X | Y 联合类型用了旧的 typing.Union 比较 3.14 下其 runtime 表示为 types.UnionType旧比较失败 影响CI 在 3.14 红无法声明支持新 Python最迷惑的是失败只在 3.14 出现本地 3.12 全过。这指向代码依赖了某个在 3.14 行为变化的运行时细节而非逻辑错。二、背景PEP 604 引入了X | Y联合类型语法自 3.10 可用。它的运行时类型是types.UnionTypeimport types assert isinstance(int | str, types.UnionType)但很多类型 introspection代码写于 PEP 604 之前习惯用typing.Union来判断联合import typing def is_union(t): return typing.get_origin(t) is typing.Union # 对 X | Y 返回 None关键差异typing.get_origin(int | str)返回types.UnionType不是typing.Uniontyping.get_origin(typing.Union[int, str])返回typing.Union于是typing.get_origin(X | Y) is typing.Union永远是False。为什么偏偏 3.14 才暴露因为 3.10~3.13 期间可能测试数据都用typing.Union[...]老写法碰巧对或代码路径在旧版本没被走到而 3.14 的某些库 / 类型注解默认走X | Y新语法或 CI 升级后更多注解用新语法于是is typing.Union的比较第一次大规模失败。也可能是 3.14 对typing做了清理移除了某些旧别名兼容让新老写法差异更明显。根因是用typing.Union去比较types.UnionType的运行时联合两套表示没统一。三、根因抽象成代码示意import typing, types def is_union_buggy(t): # BUG只认 typing.Union不认 X | Y 的 types.UnionType return typing.get_origin(t) is typing.Union # 行为 is_union_buggy(typing.Union[int, str]) # True 老写法 is_union_buggy(int | str) # False 新语法3.14 大量出现根因链条X | Y语法PEP 604的运行时类型是types.UnionType旧 introspection 代码用is typing.Union判断联合typing.get_origin(int | str)返回types.UnionType不等于typing.Union比较永远 False走错分支 / 断言失败3.14 下更多注解用X | Y该 bug 首次大规模触发低版本测试数据偏老写法侥幸通过——典型的版本相关 silent 错判。一句话用typing.Union比较X | Y的types.UnionType运行时表示3.14 下因新语法普及而失败。四、最小可运行复现用纯 Python 模拟typing.Union vs types.UnionType 比较失败# repro_py314_union.py import typing import types def is_union_buggy(t): return typing.get_origin(t) is typing.Union def is_union_fixed(t): origin typing.get_origin(t) return origin is typing.Union or origin is types.UnionType def main(): old typing.Union[int, str] new int | str print(老写法 is_union? , is_union_buggy(old)) # True print(新语法 is_union(buggy)? , is_union_buggy(new)) # False - 失败 print(新语法 is_union(fixed)? , is_union_fixed(new)) # True assert is_union_buggy(new) is False, 复现新语法被旧比较误判 if __name__ __main__: main()运行输出老写法 is_union? True 新语法 is_union(buggy)? False 新语法 is_union(fixed)? Trueint | str被旧is typing.Union误判为非联合正是 3.14 测试失败的抽象。五、解决方案第一层最小直接修复最小且必须的一步把联合判断同时覆盖types.UnionType# fix_layer1.py import typing import types def is_union(t): origin typing.get_origin(t) # 修复同时识别 typing.Union 与 X | Y 的 types.UnionType return origin is typing.Union or origin is types.UnionType这一层改动最小让判断兼容两种联合表示。但散落在多处的 introspection 都要改易漏。六、解决方案第二层结构性改进把类型判断收敛成统一工具集中处理所有 3.14 相关的类型表示差异杜绝散落判断# fix_layer2.py import typing import types from dataclasses import dataclass dataclass(frozenTrue) class TypeProbe: staticmethod def is_union(t) - bool: origin typing.get_origin(t) return origin in (typing.Union, types.UnionType) staticmethod def is_optional(t) - bool: # Optional[X] Union[X, None]需同时识别 None | X 新语法 if not TypeProbe.is_union(t): return False return type(None) in typing.get_args(t) staticmethod def normalize(t): # 统一归一成 typing.Union[..., None] 形态便于比较 if TypeProbe.is_union(t): args typing.get_args(t) return typing.Union[args] return t # 用法 assert TypeProbe.is_union(int | str) assert TypeProbe.is_optional(int | None)要点TypeProbe集中所有类型判断新版本差异只改这一处is_optional也兼容X | None新语法normalize把X | Y和Union[X, Y]归一成同一形态比较不再受写法影响。七、解决方案第三层断言 / CI 守护写 pytest 验证3.14 的新语法写法被正确识别且 CI 矩阵必须包含 3.14# test_py314_types.py import typing import types import pytest def is_union(t): origin typing.get_origin(t) return origin in (typing.Union, types.UnionType) def test_new_union_syntax(): assert is_union(int | str) is True def test_old_union_still_works(): assert is_union(typing.Union[int, str]) is True def test_optional_new_syntax(): args typing.get_args(int | None) assert type(None) in args def test_nested_union(): assert is_union(int | str | float) is True并把 3.14 加进 CI 矩阵GitHub Actionsmatrix.python-version包含3.14确保任何版本相关回归都能被红。八、排查清单CI 在 Python 3.14 失败时确认失败是否仅 3.14、低版本全过——是则高度怀疑版本相关行为变化看报错是否围绕typing/types/ 语法联合类型、泛型、get_origin搜is typing.Union/typing.get_origin(...) is这类脆弱比较补上types.UnionType按第五 / 六节用TypeProbe统一类型判断把 3.14 加进 CI 矩阵挡住未来的版本相关回归同理检查inspect、asyncio、importlib.resources等 3.14 有变动的模块用第七节的 pytest 守护新语法写法被识别。九、小结Python 3.14 测试失败根因常是类型 introspection 用typing.get_origin(t) is typing.Union判断联合而 PEP 604 的X | Y语法运行时是types.UnionType两者不相等。旧typing.Union[...]写法侥幸通过3.14 下新语法普及后误判爆发。三层层级第一层联合判断同时覆盖typing.Union与types.UnionType第二层用TypeProbe集中所有类型判断与归一化版本差异只改一处第三层pytest 验证新语法被识别并把 3.14 加进 CI 矩阵。核心教训任何依赖运行时类型标识做比较的代码在 Python 大版本升级时都易碎。用in (A, B)覆盖多种表示、并收口到单一工具比散落的is X比较稳得多。CI 矩阵永远要包含最新 Python让版本相关回归当场变红。