首页 / 文章 / 你真的要跑五个 Python 类型检查器了吗?
← 返回
安全技术

你真的要跑五个 Python 类型检查器了吗?

✍️ zhirenhun 📅 2026/6/9 👁 91 阅读 ⏱ 11 分钟
你真的要跑五个 Python 类型检查器了吗?

你真的要跑五个 Python 类型检查器了吗?

Mypy、Pyrefly、Pyright、ty、Zuban——未来可能还会更多……库维护者们该怎么办?

TL;DR:优先在你的测试套件上跑尽可能多的类型检查器。在你的源代码上至少跑一个就行。

最重要的类型检查(以及为什么你可能搞反了)

如果你只读这一篇文章的一个段落,请读这个。因为这是很多包都搞错的地方。常见的现象是:包在源代码上跑类型检查器,但测试代码却不加类型注解。这种思路搞反了方向

假设你维护一个 Python 包。作为一个假想的用户,我并不特别关心你的内部开发实践。你用 ruff format 还是 black,你怎么排序导入,你用 pytest 还是 unittest——这些都与我无关。我在意的是你的公共 API 以及我使用它的体验。

当你在内部源代码上跑类型检查器时,你主要是在测试你的内部逻辑。你可以用你喜欢的任意类型检查器来做这件事,那是你的选择。但你的用户用什么类型检查器,这个不由你决定。

通过在测试套件上运行尽可能多的类型检查器,你能够确保你的包的公共 API 对尽可能多的用户都工作良好。

Polars 的故事

Polars 是一个现代的 DataFrame 库,自 2020 年发布以来,它在数据科学领域掀起了一场风暴。作为该库的重度用户,我非常希望让它的开发者体验变得更好。如果 Polars 的类型是准确的,那么作为用户,我就能获得更好的自动补全、文档和某些类型 bug 的防护。那么,把 Pyrefly 加入 Polars 的持续集成工作需要什么呢?

我开始调研这件事,很快就遇到了障碍。Pyrefly 通常比 mypy 更严格,所以我需要重写部分代码库,或者在实例化变量时添加更明确的类型注解。此外,我还遇到了一些 Pyrefly 的 bug,好消息是,其中绝大多数 bug 的修复已经随备受期待的 v1 版本一并发布了。我认为这是值得的——它发现了一个中级优先级的 bug——但我确实问过自己,为另外三个类型检查器再做一遍同样的事情值不值得。

为了说明这一点,我们来看一下 DataType.__eq__ 这个方法。在 Python 中,任何 __eq__ 方法都应该返回 bool,如果不是这样,我们需要显式告诉类型检查器忽略这个类型错误。Polars 中的这个函数还可以根据输入的不同返回不同的类型,因此需要 重载(overloads)。为了让这个函数同时满足 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:
    # check (in)equality behaviour of temporal types that take units
    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,不过这是一个更大的工程,正在逐步推进。

那我的源代码呢?为什么有这么多类型检查器?

类型注解规范概述了一套类型检查器应当遵守的标准规则。不过规范中的某些方面有些模糊,例如当用户没有充分指定类型信息的情况。在这些情况下,不同的类型检查器做出了不同的设计决策:

  • 有些选择尽可能严格——必要时宁可误报,也要尽可能保护你免受潜在 bug 的侵害。
  • 其他的则选择更宽松,允许你更渐进地在代码库中添加类型信息。

说到对源代码进行类型检查时,你最好问问自己:你想处在严格与宽松光谱的哪个位置?Pyrefly 不仅严格(虽然这是可配置的),而且快速符合规范,是一个极好的选择。如果你在你的项目中试用它并遇到任何问题,请报告问题,这样你和所有其他用户都能从修复中受益!

结论

目前有 5 个 Python 类型检查器受到关注:mypy、Pyrefly、Pyright、ty、Zuban。库维护者可能会觉得在源代码上跑全部 5 个检查器维护成本太高,而且需要用太多类型忽略注释污染代码。我们论证了,更好的做法是把这些精力花在跑多个类型检查器来检查测试上,因为这样可以测试库在用户使用时是否能被正确类型检查。


原文:Are you really expected to run five type-checkers now? | Pyrefly — Marco Gorelli, Quansight Labs

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

Python 类型检查 Pyrefly Pyright
← 上一篇
小米 MiMo-V2.5-Pro-UltraSpeed:千亿参数模型生成速度突破 1000 TPS
下一篇 →
PostgreSQL 19 前瞻:查询提示(Query Hints)全面解读

📌 相关推荐

漏洞报告不再特殊
2026/6/24
Curl 将于 2026 年 7 月暂停接受漏洞报告
2026/6/15
德国法院里程碑式裁定:Google 对 AI Overviews 虚假内容承担直接责任
2026/6/14
← 返回文章列表