ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Pyrefly 生态视角下的 NumPy 类型完整性工程:从 33% 到近 90% 的实战复盘

Pyrefly 生态视角下的 NumPy 类型完整性工程:从 33% 到近 90% 的实战复盘 Pyrefly 生态视角下的 NumPy 类型完整性工程从 33% 到近 90% 的实战复盘【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly本篇技术指南完整复盘了 Quansight Labs 与 Meta Pyrefly 团队合作将 NumPy 的 type-completeness类型完整性得分从约 33% 提升至近 90% 的全过程从pyright --verifytypes度量口径的校正到一行修复让覆盖率翻倍再到通过大规模 overloads 编写将MaskedArray补全到 100%。读完本文你将掌握 type-completeness 的准确度量方法、stub 编写中 overloads 的设计思路以及 Pyrefly 生态中形状感知 NumPy stubs 与一致性检查工具的工程实践。什么是 type completeness现代 IDE 依赖类型注解为开发者提供补全建议、跳转导航与语法提示。Pyright 是目前流行的类型检查器之一它除了检查类型正确性与一致性之外还能度量一个库的公共 API 中有多大比例带有类型注解。我们把一个库导出的完全已类型化符号所占的百分比称为type-completeness 得分。举个例子假设某个模块导出了foo和bar两个函数def foo(a: int): return None def bar() - int: return 1它的 type-completeness 得分就是 50%因为foo缺少返回类型注解属于部分未知partially unknownbar参数与返回值均有注解是 type-complete 的。如果把foo的签名改成def foo(a: int) - None:得分就会直接跳到 100%。一个库的类型越完整IDE 能为用户提供的提示就越有价值——这也是投入大量精力去补全库类型的根本原因。需要特别说明的是type-completeness 只度量公共 API至少是 Pyright 已知的部分被类型覆盖的比例它并不验证这些类型本身的正确性与自洽性。若要验证类型标注正确还需要运行类型检查器。目前最常用的类型检查器是 mypy 和 Pyright而 Pyrefly 与 ty 也因其出色的性能表现吸引了大量关注需要注意的是两者当时都尚未自称达到生产就绪尝试时应对预期有所保留。数字起点NumPy 的 type-completeness 到底是多少2025 年 3 月团队第一次尝试度量 NumPy 的类型完整性时直接运行pyright --verifytypes numpy输出的完整度得分是18%。这个数字低得反常——毕竟此前 NumPy 的类型化工作已经持续了相当长时间。仔细检查输出后发现问题出在度量口径上而非 NumPy 真的只有 18%某些对象例如DTypeLike明明带有类型注解却被报告为部分未知类型完整性报告把numpy.tests.test_matlib这类测试模块也计入了统计。第一个问题的根源是 NumPy 的代码导入了一个部分未类型化的标准库模块decimal。由于这超出了 NumPy 的可控范围团队按照建议通过--ignoreexternal参数把它从覆盖率报告中排除。第二个问题则有现成的处理手段Pyright 支持用--outputjson将覆盖率报告导出为 JSON随后解析 JSON 并排除numpy.tests。虽然 Pyright 将测试视为公共 API但 NumPy 的普通用户并不会直接与内部测试套件交互因此排除测试以聚焦最影响用户体验的部分是合理的选择。完成上述两步校准后基线得分修正为33%——这才是真正的起点团队随后的工作围绕剩余 67% 展开。一行修复让覆盖率翻倍ndarray 的CanIndex拼写错误Pyright 的报告覆盖类、方法、函数、类型别名等各类符号。科学计算生态的许多代码都围绕numpy.ndarray这样的核心类展开而ndarray当时被报告为部分未知。但用 Python 快速统计导出的相关符号后发现情况与直觉不符 np.mean([x[isTypeKnown] for x in exported if x[name].startswith(numpy.ndarray.)]) np.float64(0.9811320754716981)ndarray虽然整体被标记为部分未知但其98% 的方法其实都已有已知类型。把它补到 100% 的代价应该很小。事实正是如此——只需要一行修改修正一处类型注解中的拼写错误- def setfield(self, /, val: ArrayLike, dtype: DTypeLike, offset: CanIndex 0) - None: ... def setfield(self, /, val: ArrayLike, dtype: DTypeLike, offset: SupportsIndex 0) - None: ...仅此而已。CanIndex是一个未知符号很可能是笔误将它替换为正确的SupportsIndex之后NumPy 的整体 type-completeness 直接跃升到80% 以上。这个案例也说明在大规模 stub 工作中已标注但引用了未知符号的隐形欠账往往比完全未标注更能拉低统计得分值得优先排查。主战场 MaskedArray从 20% 到 100%随后团队开始逐个检查 NumPy 的其他类寻找更大的突破口。统计MaskedArray类已类型化符号的比例时发现了一个鲜明的反差 np.mean([x[isTypeKnown] for x in exported if x[name].startswith(numpy.ma.core.MaskedArray.)]) np.float64(0.2)只有20%的符号有类型——而同期ndarray已经达到 98%。更关键的是MaskedArray是一个使用相当广泛的类出现在 pandas、scikit-learn、xarray 的代码库中。如此糟糕的类型覆盖与如此广泛的使用场景形成强烈对比使它成为投入时间补全的理想候选。难点在于 overloads而非参数推断给 NumPy 代码补类型的主要困难并不是用自动化工具推断可能的参数取值这很容易而是处理数量庞大的 overloads重载。原因在于 NumPy 大量方法的返回类型取决于输入类型的具体组合。以MaskedArray的实例ma为例统计非空值的数量就有多种情况ma.count()返回一个整数ma.count(axis0)返回一个数组ma.count(keepdimsTrue)也返回数组且形状与输入数组保持一致。要精准描述这些语义就需要为每种情况编写一个不同的 overload。上面还只是个相对简单的例子有些方法的必要 overloads 数量高达 9 个。这类工作很难自动化需要仔细研读文档与源码逐条手工编写工作量相当可观。最终在及时的评审支持下MaskedArray被报告为100% type-completeNumPy 的整体 type-completeness 也由此推进到88%。仓库侧的同向努力Pyrefly 生态中的形状感知 NumPy stubs与这份博客对应的是当前仓库中同样可以看到 Pyrefly 在 NumPy 类型方向上的持续投入——即 tensor-shapes/pyrefly-numpy-stubs 这套带数组形状信息shape information的 NumPy 类型 stubs它是一个遵循 PEP 561 的stub-only 发行包安装numpy-stubs存根包让 Pyrefly 能发现形状感知的 stubs同时不替换、不遮蔽运行时真正的numpy包本身它的版本与 Pyrefly 保持同步lockstep并依赖配套的pyrefly-shape-extensions包。其覆盖率配置 stub_coverage.toml 直接体现了围绕ndarray这类核心类做类型保障的思路runtime_package numpy stub_directory numpy-stubs skip_modules [numpy._shapes] [[member_targets]] stub_module numpy stub_class ndarray runtime_module numpy runtime_class ndarray该配置声明了运行时包、stub 目录并通过member_targets将 stub 中的numpy.ndarray与运行时类建立对照关系。测试侧则由 suites.py 按文件名自动发现test/test_*.py形成测试套件每个套件都带expectations测试文件中用# E:标记与 NumPy 运行时实际抛出的错误配对断言让类型检查拒绝与运行时行为两方面同时得到验证。此外scripts/stub_coverage.py实际位于 tensor-shapes/stub_coverage.py实现了检查部分 stub 包与其所遮蔽库的一致性通过Config结构runtime_package、stub_directory、skip_modules、member_excludes、member_targets等字段与 pyrefly-numpy-stubs 的 stub_coverage.toml 对接。从源码结构看这套工具链承担着持续追踪stub 声明的成员是否与运行时 NumPy 对齐的职责正是博客中所强调的类型不仅要有、还要正确这一理念在工程上的落地。更广泛地说Pyrefly 自 v0.40 起随发行版捆绑 Typeshed 第三方 stubs自 v0.46.0 起进一步扩展捆绑 Boto3、Pandas、Matplotlib、SciPy 系等第三方库的 stubs见 website/blog/2026-02-17-stubs.md 与 crates/pyrefly_bundled/third_party/stubs从 IDE 补全、inlay hints 到 hover 类型展示全面受益——这与 NumPy 类型补全的目标殊途同归让类型信息真正进入开发者日常。还缺什么NumPy 类型工作的下一步尽管整体已推进到 88%团队也明确列出了剩余的工作清单顶层未类型化函数MaskedArray模块中仍存在部分未类型化的顶层函数例如numpy.ma.count更精确、形状保持的 overloads例如输入是 2D 数组时尽量在输出类型中保留这一形状信息stubs 中缺失的默认值missing defaults这方面的工作空间并不小。然而最大的缺口是房间里的大象NumPy 并未在其 CI 中运行类型检查器。它确实有一些类型测试但这与在 CI 中真正运行类型检查器是两回事。如果有读者对开源贡献感兴趣让 NumPy 的类型状态达到可以稳定运行某个类型检查器甚至运行 stubtest的程度会是一项非常有价值的投入。结语与致谢本文完整回顾了 NumPy type-completeness 从约 33% 提升到近 90% 的过程先校准--verifytypes的度量口径排除外部未类型化依赖与测试模块再用一行拼写修复CanIndex→SupportsIndex将ndarray补满、整体突破 80%最后通过大规模手工 overloads 将MaskedArray从 20% 提升到 100%。鉴于 NumPy 在生态中的广泛采用程度这一努力的边际影响被显著放大更好的类型意味着更出色的 IDE 体验也让 pandas、scikit-learn、xarray 等下游库能够编写更安全的代码。感谢 Meta 与 Quansight Labs 对这项工作的资助与推动。【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表