现在需要运行五个 Python 类型检查器吗?

Hacker News Top 工具

摘要

一篇博文主张 Python 库维护者应优先在测试套件中运行多个类型检查器,以确保公共 API 的兼容性,并强调了兼容性和代码污染方面的挑战。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/08 15:17

# 现在真的需要运行五个类型检查器吗? 来源:https://pyrefly.org/blog/too-many-type-checkers/ Mypy、Pyrefly、Pyright、ty、Zuban,以及未来可能还会出现的更多……库维护者该如何应对? **太长不看**:优先在测试套件中运行尽可能多的类型检查器。至少要在你的源码上运行一个。 ## 最重要的类型检查(以及为什么你可能搞反了) (https://pyrefly.org/blog/too-many-type-checkers/#the-type-checking-that-matters-most-and-why-youve-probably-got-it-backwards) 如果你只打算读本文的一个章节,请读这一节。因为这是很多包出错的地方。常见的做法是在源码上运行类型检查器,而测试代码却不加类型注解。**这种做法搞反了**。 假设你维护一个 Python 包。作为你代码的假设用户,我其实不太关心你的内部开发实践。你是用 `ruff format` 还是 `black`,怎么排序导入,用 `pytest` 还是 `unittest`,这些都不影响我。我在意的是你的公共 API 以及我使用它的体验。 当你在内部源码上运行类型检查器时,你主要是在测试内部逻辑。你可以用任何你喜欢的检查器来做这件事,那是你的选择。但你的用户使用哪个检查器,则不是你能决定的。 通过在测试套件上运行尽可能多的类型检查器,你能确保你的包的公共 API 对尽可能多的用户都工作良好。 ## Polars 的故事 (https://pyrefly.org/blog/too-many-type-checkers/#the-polars-story) Polars 是一个现代数据框库,自 2020 年发布以来,在数据科学领域风靡一时。作为该库的重度用户,我非常希望进一步提升它的开发者体验。如果 Polars 的类型是准确的,那么作为用户,我就能获得更好的自动补全、文档,以及免受某些类型 bug 的侵害。那么,将 Pyrefly 加入 Polars 的持续集成任务需要做些什么呢? 我开始研究这个问题,很快就遇到了一些障碍。Pyrefly 通常比 mypy 更严格,因此需要重写部分代码或为变量实例化添加更明确的类型注解。此外,我还遇到了一些 Pyrefly 的 bug,令人鼓舞的是,其中大部分 bug 的修复已随备受期待的 v1 版本发布。我认为这是值得的,尤其是它发现了一个中等优先级的 bug,但我不得不问自己,再为另外三个类型检查器重复这个过程是否值得。 为了说明这一点,我们来看 `DataType.__eq__` 这个函数。在 Python 中,任何 `__eq__` 方法都应返回 `bool`,如果不这样做,就需要显式告诉类型检查器忽略类型错误。Polars 中的这个函数还可以根据输入返回不同的类型,因此需要重载。为了让这个函数同时满足 mypy、Pyrefly 和 ty,我们需要这样写: ``` @overload # type: ignore[override] def __eq__( # pyrefly: ignore[bad-override] self, other: pl.DataTypeExpr ) -> pl.Expr: ... @overload def __eq__(self, other: PolarsDataType) -> bool: ... def __eq__(self, other: pl.DataTypeExpr | PolarsDataType) -> pl.Expr | bool: # ty: ignore[invalid-method-override] # pyright: ignore[reportIncompatibleMethodOverride] ``` 哇,仅仅 7 行代码就用了 4 个不同的类型忽略注释!你可以想象代码库很快就会充斥这样的注释,或者为了应付不同类型检查器的怪癖而引入的变通方案。我认为没有哪个库维护者想要这样的代码库。肯定有更好的办法吧? 与其把所有内部代码都交给多个类型检查器审查,为什么不先测试一下所有主流类型检查器都能正常使用你库的公共 API 呢?这有用得多,因此也更容易证明花时间在上面是值得的。而且这也更简单,因为你只需要确保你的库按预期使用时不会出现类型错误。以 `DataType.__eq__` 为例,有一个测试是这样的: ``` DTYPE_TEMPORAL_UNITS: Final[frozenset[TimeUnit]] = frozenset(["ns", "us", "ms"]) def test_dtype_time_units() -> None: # 检查带单位的时间类型的(不)相等行为 for time_unit in DTYPE_TEMPORAL_UNITS: assert pl.Datetime == pl.Datetime(time_unit) assert pl.Duration == pl.Duration(time_unit) assert pl.Datetime(time_unit) == pl.Datetime assert pl.Duration(time_unit) == pl.Duration ``` 令人欣慰的是,mypy、Pyrefly、Pyright、ty、Zuban 都能正确检查这段代码而不报任何错误!所以,尽管类型检查器在实现上存在一些分歧,但它们对公共 API 的效果看法一致。而这正是你的用户关心的! 让 Pyrefly 运行整个 Polars 测试套件相对轻松,你可以查看这个 PR 来验证。为了简化 Polars 自身的内部开发,我们也在探索在它们的源码上使用 Pyrefly,不过这是一个更大的工程,正在逐步推进。 ## 那我的源码呢?为什么会有这么多类型检查器? (https://pyrefly.org/blog/too-many-type-checkers/#what-about-my-source-code-why-are-there-so-many-type-checkers-anyway) typing 规范规定了一套类型检查器应遵守的标准规则。然而,在某些方面它有些模糊,比如当用户未充分指定类型信息时。在这些情况下,不同的类型检查器会做出不同的设计决策: - 有些选择尽可能严格,必要时宁可误报,也要尽最大努力保护你免受潜在 bug 的侵害。 - 其他则更宽松,允许你逐步向代码库添加类型信息。 在检查源码时,你需要问自己希望处于严格与宽松之间的哪个位置。Pyrefly 不仅严格(尽管可以通过配置调整),而且速度很快、符合规范,因此是一个极好的选择。如果你在自己的项目中试用它并遇到任何问题,请报告问题,这样你和它的所有其他用户都能从修复中受益! ## 核心要点 (https://pyrefly.org/blog/too-many-type-checkers/#the-bottom-line) 目前有 5 个 Python 类型检查器备受关注:mypy、Pyrefly、Pyright、ty、Zuban。库维护者完全有理由觉得,在这 5 个检查器上运行源码维护成本太高,并且会让代码充斥过多的类型忽略注释。我们论证过,更好的做法是将这些精力花在在测试上运行多个类型检查器上,因为这样可以测试库在用户使用时的类型检查效果如何。

相似文章

PyCon US 2026 类型峰会回顾

Lobsters Hottest

本文回顾了 PyCon US 2026 类型峰会,详细介绍了关于 Python 类型化进展的关键演讲,包括 PEP 提案、AI 辅助类型检查实验以及类型委员会问答环节。

用 LLM 揪出 Python C 扩展的 bug

Lobsters Hottest

Daniel Diniz 借助 Claude Code 与自研插件,系统性地在 44 个 Python C 扩展项目中挖出 575+ 个 bug,其中 14 个项目已合并修复。

插件案例研究:Pluggy

Eli Bendersky

一篇博客文章,探讨了Pluggy——这是一个最初源自pytest的Python库,用于构建插件系统。内容包括其工作原理以及如何将其与一个玩具级HTML转换工具配合使用。