现在你是否需要运行五个 Python 类型检查器?
Mypy、Pyrefly、Pyright、ty、Zuban,以及未来可能会出现的更多……库维护者该如何应对?简而言之:在你的测试套件上优先运行尽可能多的类型检查器。至少在你的源代码上运行一个。最重要的类型检查(以及为什么你可能搞错了)如果你只阅读这篇博客文章的一节,请务必阅读这一节。因为这里是许多包出错的地方。常见的情况是看到包在其源代码上运行类型检查器,而将测试留空。这种方法是错的。假设你维护一个 Python 包。作为你代码的假想用户,我并不特别关心你的内部开发实践。无论你使用 ruff 格式还是 black,如何排序导入,是否使用 pytest 或 unittest,这些都不会影响我。而我关心的是你的公共 API 以及我与之交互的体验。当你在内部源代码上运行类型检查器时,你主要是在测试你的内部逻辑。你可以使用你喜欢的任何类型检查器,这由你来选择。然而,用户使用哪个类型检查器则不是你能控制的。通过在你的测试套件上运行尽可能多的类型检查器,你可以确保你的包的公共 API 对尽可能多的用户良好工作。Polars 的故事Polars 是一个现代数据框库,自 2020 年推出以来,在数据科学界引起了极大反响。作为该库的重度用户,我非常希望能让我在使用过程中获得更好的开发体验。如果 Polars 的类型准确,那么作为用户,我可以获得更好的自动补全、文档以及对某些类错误的保护。要将 Pyrefly 添加到 Polars 的持续集成工作中需要做些什么?我开始调查这个问题,但很快遇到了一些障碍。Pyrefly 通常比 mypy 更严格,因此需要重写代码库的部分内容或在实例化变量时添加更多明确的类型注解。此外,我在 Pyrefly 中遇到了一些错误,令人鼓舞的是,大多数错误的修复都随备受期待的 v1 版本发布而推出。我认为这值得,特别是它揭示了一个中优先级的错误,但我确实不得不考虑,是否还需要为其他三个类型检查器进行这样的工作。为了说明这一点,我们来看看函数 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 : # 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,尽管这是一项更大的工作,正在逐步解决。我的源代码呢?为什么有这么多人...
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡