修正 Pyright 和 Basedpyright 的偏执行为

comp
@python
zh-Hans
#opinion

Pyright 是一个 Python 类型检查器,而 Basedpyright 是 的一个非理智的分支。

TL;DR

# pyproject.toml

[tool.pyright]
enableTypeIgnoreComments = true
reportIgnoreCommentWithoutRule = false

reportAny = false
reportExplicitAny = false
reportInvalidCast = false
reportRedeclaration = false
reportPrivateImportUsage = false
reportMissingModuleSource = false
reportUnknownMemberType = false
reportUnknownArgumentType = false
reportUnknownParameterType = false
reportUnknownVariableType = false
reportUnknownLambdaType = false

你说得对,但 Pyright 是由 Microsoft 开发的一款 Python Language Server。尽管由 Node.js 驱动,这款 LSP 的性能甚至超过竞品,并通过捆绑销售在 Visual Studio Code 中广泛被使用。但 VSC 中提供的是加入了额外闭源功能的变体 Pylance,因此在其他编辑器中使用体验不佳或直接被封禁。Basedpyright 是一个尝试向 Pyright 添加更多 LSP 功能的社区分支,其中的 inlay hint 等功能十分实用,但这一分支尽管自称 based,但仍然引入了一些偏执的选项——这与 Pylance 行为不一致,且在基础类型检查(basic)中默认开启。本文记录如何修正这些偏执的选项。

特别注意,我们不考虑 baseline 文件。在被迫使用别人制造的💩️时,严于律己没有任何裨益。

Ignore 注释

Basedpyright 默认禁用 # type: ignore 而是要求换用 # pyright: ignore[<理由>]。这听起来像是 TypeScript 社区的优秀实践,但实际上,由于 Python 的渐进类型系统和静态类型检查以搞笑为主,常用的第三方库代码质量经常一团糟,# type: ignore 成了强力压制暴走的类型检查器或流氓第三方库的居家必备之佳品。在很多情况下,用户根本不想解释理由(给类型检查器的注释有时比实际代码都长?),并向代码直接扔了一个 # type: ignore,这是 PEP 484 所准许的。因此,通过 enableTypeIgnoreComments = truereportIgnoreCommentWithoutRule = false 强制恢复标准行为。

Any

作为类型系统的顶端,Any 在面对别人的💩代码时是很难避免的,对于一个动态类型语言来说它也非常本质。但 Basedpyright 偏要默认警告隐式显式的 Any 用例,惨绝人寰,因此将 reportAnyreportExplicitAny 都关闭。此外,TypeScript 中强制转换到不兼容的类型写作 as any as T,在对结构化类型(structural typing)支持十分苟且的 Python 中用 typing.cast 本可以一步到位,但 Basedpyright 又将其封堵,因此关闭 reportInvalidCast

Unknown

Pyright 系列中引入了一个 Python 类型系统中不存在的 Unknown 类型,这个类型和 TypeScript 的 unknown 不同,实质上等价于隐式的 Any,允许进行任何操作(相比之下,TypeScript 的 unknown 不允许进行任何操作,除非强制类型转换)。 Unknown 无非以下情况:使用了没有类型标注的代码,类型检查器推不出来类型,或者这个类型在 Python 里完全写不出来直接摆烂。因此,禁止 Unknown 的出现大概率是自讨苦吃。

Pyright 一共实现了五条 Unknown 规则:reportUnknown{Member, Argument, Parameter, Variable, Lambda}Type。其中,reportUnknownLambdaType 更是重量级,因为 Python 的 lambda 表达式里面根本不支持标注任何类型。在 Pyright 中,这几条规则都被设为了关闭,只在 strict 模式下打开,但 Basedpyright 把它在 recommend 模式下设为了警告,这对于主要使用第三方库(即:面对别人的💩代码)的场景来说十分不友好。但经测试,似乎 Basedpyright 1.29.0 在关闭类型检查的情况下也会爆警告,只好填配置通通关闭掉。

遮蔽

在 Python 中遮蔽已经定义过的名字是常见的用例——因为这是一门动态语言!尽管应当尽力避免这种用法,但在中间变量过多的情况下它让代码变得更可读,且有时需要在运行时动态遮蔽不支持的实现(implementation)。因此无论 Pyright / Pylance 还是 Basedpyright 都应当考虑关闭 reportRedeclaration

其他

  • reportPrivateImportUsage,对于质量非常差且不再更新维护的第三方库来说,包装其底层实现是合理的。
  • reportMissingModuleSource,人家模块有没有实现不关类型检查器的事——也许这是运行时动态注入实现呢?