Pyrefly v1.2.0-dev.1 版本深度解析:attrs 全面支持、LSP 体验升级与 Tensor Shape 运行时增强
【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly
导读:Pyrefly 是面向 Python 的高性能类型检查器与语言服务器(本仓库即其完整开源实现)。v1.2.0-dev.1 是自主干定期切出的开发快照,打包 235 个提交、38 位贡献者的成果,覆盖类型检查、语言服务器、Stubgen、Tensor Shape 类型系统四大方向的增强与 20 个缺陷修复。本文以该版本说明为核心骨架,结合仓库内的官方文档、实现源码与测试配置逐项展开,帮助读者理解每个新能力的实际用法、底层原理与升级注意事项。
一、版本概览:dev 快照的定位与升级方式
v1.2.0-dev.1 属于开发版(dev release)。官方说明明确指出:X.Y.Z-dev.N这类版本是从主干(trunk)定期切出的非稳定快照,目的是让早期采用者提前试用进行中的特性、在下一个稳定版发布前暴露问题。它不承担与稳定版相同的稳定性与兼容性保证——官方明确建议不要将生产项目锁定在 dev 版本上。
本版本从 v1.1.x 分支之后的开发周期中收集了235 个提交,由38 位贡献者共同完成。升级命令为:
pip install --upgrade pyrefly==1.2.0-dev.1注意:仓库中后续稳定版 release-notes-v1.2.0.md 显示,正式版最终打包了 901 个提交与 59 位贡献者,本文描述的能力最终都汇入了稳定版。
二、类型检查:四大核心增强
2.1 attrs 类全面支持
本版本最重量级的变化是 attrs 库的完整支持。Pyrefly 现在能够识别@attr.s、@define、@frozen及其各类变体,并完成字段综合(field synthesis)、校验与特殊方法生成,覆盖字段说明符(attr.ib()、field())、转换器(converter)、校验器(validator)、默认值以及私有字段别名(private-field aliasing)。
仓库中 attrs 官方文档 对这一能力做了完整展开,以下是该文档与版本说明相互印证的细节:
支持的装饰器与命名空间。Pyrefly 同时支持现代 API(@define、@frozen、@mutable、attrs.field)与经典 API(@attr.s、@attr.ib、@attr.dataclass),且跨越attr.与attrs.两个命名空间。不需要任何插件或手动配置,开箱即用:
from attrs import define @define class C: x: int y: int | None = None reveal_type(C.__init__) # (self: C, x: int, y: int | None = ...) -> None c = C(1)字段收集规则(auto_attribs)。Pyrefly 严格遵循 attrs 的字段归属规则,且逐类判定,基类与子类可使用不同风格并互相继承字段:
@define/@mutable/@frozen:从注解收集字段;出现未注解的field()/attr.ib()时切换为"仅说明符"模式;- 经典
@attr.s默认auto_attribs=False:只有attr.ib()/field()赋值算字段,裸注解被忽略; @attr.s(auto_attribs=True)与@attr.dataclass:从注解收集字段。
# 经典 @attr.s 忽略裸注解 @attr.s() class A: x: int y: int | None = None reveal_type(A.__init__) # (self: A) -> None A(1) # Error: Expected 0 positional arguments # auto_attribs=True 则从注解收集 @attr.s(auto_attribs=True) class B: x: int y: int | None = None reveal_type(B.__init__) # (self: B, x: int, y: int | None = ...) -> None字段说明符与转换器。attrs.field()、attr.ib()及其别名attr.attr()、attr.attrib()的关键字(default、factory、kw_only、alias、init、type、hash)都被理解,字段类型流入合成的__init__,构造调用因此被类型检查;同时校验 attrs 的合法性规则(如default=与factory=不可同设)。带converter=的字段,__init__参数取转换器的输入类型,而存储属性保留声明的输出类型:
from attrs import define, field def to_int(s: str) -> int: return int(s) @define class C: x: int = field(converter=to_int) reveal_type(C.__init__) # (self: C, x: str) -> None reveal_type(C("5").x) # int冻结类、装饰器关键字与校验。@frozen禁止实例属性赋值并传播到子类(冻结子类继承非冻结基类会被标记);init、frozen、kw_only、order、match_args、eq/unsafe_hash/hash、slots等标准关键字全部生效;order=True要求eq、旧式cmp不可与eq/order混用等 attrs 运行时ValueError规则也被静态执行。此外还支持@x.default/@x.validator方法装饰器(校验参数形态、默认值方法返回类型与冲突规则)、私有字段单下划线剥离(_private变为参数private,alias=可覆盖)、继承字段排序与"无默认值字段不得跟在有默认值字段之后"的排序报错,以及attr.fields()、attrs.has()、attr.evolve()/attr.assoc()等辅助函数的实参校验。
2.2 isinstance 窄化:先消费动态不确定性
isinstance窄化现在会先消费动态不确定性,再与目标类型求交。典型效果:x: Any经isinstance(x, C)窄化后精化为C,而非保留Any。官方将其定位为一次行为变更:它以牺牲渐进式保证(gradual guarantee)为代价,换取基于运行时证据的更精确窄化,堵住了一个类型安全漏洞。从源码结构看,这一改动位于窄化引擎对Any不确定性处理的环节——这是 v1.1.0 中回归问题(详见 4.1 节 #3867)的后续完善,最终在稳定版中被正式确认。
2.3 TypedDict 的 .get()/.pop() 保留字段字面量类型
当 TypedDict 的.get()/.pop()使用可赋值给字段类型的字面量默认值时,返回值现在保留字段类型。例如字段类型为Literal["a", "b"],调用.get("x", "b")返回Literal["a", "b"]而非Literal["a", "b"] | str。配套的缺陷修复 #3787 进一步覆盖了非必填键(non-required key)的场景,避免其向field_type | str拓宽。
2.4 未注解类属性:构造器赋值取并集
未注解的类属性不再取第一次赋值的类型,而是对构造器(__init__)中所有赋值的类型取并集(union)。这修正了多路径初始化场景下属性类型被第一条赋值路径"锁定"而丢失其他分支类型信息的问题。官方在稳定版说明中将其同样列为行为变更,升级后可能暴露此前被掩盖的类型错误。
三、语言服务器:IDE 体验六项升级
3.1 Baseline 错误降级为 Hint
存储在独立 baseline 文件中的已知错误,现在在 IDE 中显示为hint(提示)而非 error(错误),让开发者一眼区分"新问题"与"已知技术债"。Baseline 机制的完整用法见 错误抑制文档:
# 生成/重新生成 baseline 文件 pyrefly check --baseline="<path to baseline file>" --update-baseline # 基于 baseline 检查,只报告新引入的错误 pyrefly check --baseline="<path to baseline file>"也可写入配置(pyrefly.toml或pyproject.toml),并通过baseline-matching-mode("column"默认 /"concise-description")与baseline-format("full"默认 /"minimal")控制匹配与落盘字段。
3.2 非层级文档符号:适配 Helix 等编辑器
对于不支持层级符号(hierarchical symbols)的编辑器(如 Helix),文档符号搜索改为返回扁平SymbolInformation列表,并用点分容器名(dotted container names)表达嵌套关系,保证这些编辑器中大纲/符号跳转可用。
3.3 语义 token 覆盖 with/except 绑定
with ... as与except ... as绑定的名字现在也输出语义 token,使这些变量与普通局部绑定获得一致的高亮。
3.4 非 Python 文件直达定义
对非 Python 源文件(如.thrift)的跳转到定义,不再跳到文件顶部,而是通过文本搜索直接定位符号定义,且支持嵌套属性/枚举成员访问。
3.5 unused-type-ignore 规则:清理陈旧抑制
新增的unused-type-ignore规则用于检测没有抑制任何错误的# type: ignore注释,帮助清理过期抑制。默认关闭,需在pyrefly.toml中显式启用。仓库中的 LSP 测试配置给出了最小启用方式(见 unused_type_ignore/pyrefly.toml):
[errors] unused-type-ignore = "error"同一目录下还存在baseline_unused_type_ignore测试用例(baseline.json),用于验证 baseline 与 unused-type-ignore 的交互,即 baseline 化也正确作用于未使用抑制的诊断。
3.6 跨文件诊断:strict-spec 客户端保存即刷新
在 Zed 等 strict-spec LSP 客户端中,跨文件诊断现在会在保存时刷新。此前修改被导入的文件后需要重启语言服务器才能反映到依赖方,本版本消除了这一摩擦。
四、Stubgen:生成桩的四个正确性修复
Stubgen 是 Pyrefly 的桩文件生成器(CLI 子命令),本版本集中修复了生成代码的可解析性与语义保真问题:
- 多行括号重包装:多行的括号化注解、值与返回类型在输出时重新包进
(...),避免生成的桩触发IndentationError; - 重载 Callable 渲染:不同重载返回类型不同的 Callable 型值,渲染为
Callable[..., Incomplete]而非裸Incomplete,保留可调用性信息; - 隐式类变量标注:类体内的裸赋值(隐式类变量)在生成桩中标注为
ClassVar[...],与实例属性正确区分; __all__原样保留:当__all__是静态 list/tuple 字面量时逐字保留,使再导出(re-export)在桩中存续;- 异步生成器:按 typeshed 约定输出为带
AsyncGenerator返回注解的普通def(而非async def),消除协程 vs 生成器的混淆。
五、Tensor Shape 类型:运行时配套落地
Tensor Shape 是 Pyrefly 的维度类型检查体系。本版本有三项进展:
1. 配套包发布 PyPI。pyrefly-shape-extensions与pyrefly-torch-stubs随 Pyrefly 发布周期一并发布到 PyPI,分别提供运行时辅助与形状感知的 PyTorch 桩。
2.D[N]/D(N)运行时维度包装。形状 DSL 新增D[N](类型位置)或D(N)(值位置)运行时包装。源码层面,D类定义于 shape_extensions/init.py:D(N)构造返回SymbolicArithExpr("var", (value,)),使求值后的注解在 Python 运行时存活;SymbolicArithExpr通过运算符重载把N + 1、N * 2等符号算术保留为表达式树而非抛错,__str__负责人类可读渲染。配套的IntVar实现"类 TypeVar 但算术操作返回自身"的符号维度变量。
3.assert_shape运行时校验。assert_shape(actual, shape)在运行时断言张量形状符合预期:静态层面 Pyrefly 会像assert_type一样校验被建模的形状;运行层面见 同一文件的实现——对包含符号维度的期望形状,回退为仅校验秩(rank),否则精确比较形状元组,不匹配时抛出带清晰信息的AssertionError。defines_assert_shape装饰器允许用户自定义 assert_shape 辅助函数。
形状 DSL 的定义文件内部机制位于 dsl.py(Int/IntTuple/IntTuples域操作、is_concrete_int、concat/prod/sum/einsum等),而pyrefly-torch-stubs中像 torch/_shapes.pyi 这样的桩文件,用@type_shape_dsl_function定义eig_shape、reduce_shape等 torch 专用形状函数,以dsl.Invalid(...)表达非法输入(如维度越界、重复维度)。
六、缺陷修复精选(20 个关闭)
本版本共关闭20 个 bug 问题,以下为版本说明明确点名者:
- #3867(isinstance 窄化回归):同一 if/elif/else 链中,兄弟分支窄化另一个变量并终止后,
isinstance窄化会静默失效。根因是 lazy-builtins 优化错误地把isinstance提升到 fork 基(fork base),产生退化的 Phi 节点并解析为Never,破坏了下游窄化。 - #3893(栈溢出):检查带重载方法、且
self:注解引用协议自身的自引用协议时发生栈溢出崩溃;新增按线程的循环守卫(per-thread cycle guard)阻止重载过滤期间的无限递归。 - #3841(TypeVar 元组解包):解包受元组边界约束的 TypeVar(如
Z: tuple[str, int])时,u, v = x(x: Z)现在得到u: str, v: int,而非两者都退化为并集int | str。 - #3787:TypedDict 非必填键的
.get(key, "literal")在字面量默认值可赋值时返回字段类型,不再拓宽为field_type | str。 - #3561:
@no_type_check装饰的函数不再发出unannotated-return诊断——该装饰器显式将函数排除出类型检查。 - #3900:dataclass 与 Pydantic BaseModel 中名为
"self"的字段不再在构造时被误报bad-keyword-argument/bad-argument-type;存在名为self的字段时,实例接收者改名为__dataclass_self__。 - #3881:SQLAlchemy 声明式基类
kw_only=True现在正确约束子类字段,消除"无默认值字段跟在有默认值字段之后"的误报。 - #2858:无真实窄化名的 match 主体(如
match f(x):)现在跨 case 携带直接 fallthrough 窄化,最终捕获(capture)转发到已求值且被先前 case 窄化过的主体。 - #3445:带 PEP 561
py.typed标记的包即使以子模块方式导入,也不再触发untyped-import建议。 - #3954:配置根目录下的
typings/目录现在在CLI 路径上也被自动发现(此前仅 IDE 生效),消除了同一配置下 CLI 与 IDE 桩搜索路径不一致的分叉。
其余关闭项还包括 #3879、#3891、#3293、#3928、#3688、#3890、#3912、#3945、#3926、#3924 等。
七、升级指南:安全迁移到新版本
升级 Pyrefly 或第三方依赖后,新检查规则可能暴露既有类型错误。一次性全修往往不现实,官方提供了临时抑制 + 分批修复的脚本化流程:
pyrefly check --suppress-errors # 1. 自动为所有错误添加 # pyrefly: ignore 注释 # 2. 运行你惯用的代码格式化工具 pyrefly check --remove-unused-ignores # 3. 移除已无用的抑制注释 # 4. 重复执行,直到格式检查与类型检查双双干净该流程向代码中写入# pyrefly: ignore注释,使你能够先静默错误、获得干净信号,再逐步回去修复,让大型代码库的升级可控。抑制注释的更多形式(行内注释、# pyrefly: ignore[code]按错误码定向、文件级# pyrefly: ignore-errors[code]前置指令、pyrefly suppress --comment-location=same-line等)详见 错误抑制文档;错误码的完整语义与misplaced-ignore等新增警告可参考 error-kinds 文档(该文档确认:文件级 ignore-errors 指令置于首个代码行之后即失效并触发misplaced-ignore警告,升级后请留意此类新警告)。
八、小结
v1.2.0-dev.1 清晰地勾勒出 Pyrefly 的三个攻坚方向:类型精度(attrs 全面支持、isinstance 基于运行时证据的窄化、TypedDict 字面量保真、构造器赋值并集推断)、IDE 体验(baseline 分级、语义 token 补全、非层级文档符号、跨文件诊断实时刷新)与生态工程化(Stubgen 语义保真、Tensor Shape 运行时包落地、typings/目录 CLI 一致化)。对于想要抢先验证新特性的早期采用者,可按上文升级流程安装1.2.0-dev.1;对于生产项目,建议等待包含全部 901 个提交、59 位贡献者成果的稳定版 v1.2.0,并对照 正式版发布说明 中列出的完整行为变更清单做升级评估。
【免费下载链接】pyreflyA fast type checker and language server for Python项目地址: https://gitcode.com/GitHub_Trending/py/pyrefly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考