修正 Pyright 和 Basedpyright 的偏执行为

comp
@python
zh-Hans
#opinion

Pyright 是一个 Python 类型检查器,而 Basedpyright 是 Pyright 的一个热衷于 PUA 用户的分支。

TL;DR

以下是关闭 Basedpyright 偏执行为的一种可能配置。可以放置在 pyproject.toml 下,或者转换为 JSON 格式放在 pyrightconfig.json

# pyproject.toml
 
[tool.pyright]
enableTypeIgnoreComments = true
reportIgnoreCommentWithoutRule = false

reportAny = false
reportExplicitAny = false
reportInvalidCast = false
reportPrivateImportUsage = false
reportMissingImports = false
reportMissingModuleSource = false
reportMissingParameterType = false
reportMissingTypeArgument = false
reportMissingTypeStubs = false
reportUnknownMemberType = false
reportUnknownArgumentType = false
reportUnknownParameterType = false
reportUnknownVariableType = false
reportUnknownLambdaType = false
reportUnusedCallResult = false
reportRedeclaration = 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,人家模块有没有实现不关类型检查器的事——也许这是运行时动态注入实现呢?