十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ruff ty 类型检查器系列:list 下标(Subscript)的类型推断与静态检查规则详解

Ruff ty 类型检查器系列:list 下标(Subscript)的类型推断与静态检查规则详解 Ruff ty 类型检查器系列list 下标Subscript的类型推断与静态检查规则详解【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读本篇文章围绕 Ruff 仓库中ty类型检查器的 Markdown 测试套件文档 subscript/lists.md 展开系统讲解类型检查器对list 下标操作subscript的类型推断规则与诊断行为包括用整数、切片索引 list 时的返回值类型推导下标赋值时的类型校验以及海象表达式walrus:参与下标时的类型揭示type revealing语义。读完本文你将掌握 ty 对 list 下标这一高频 Python 语法的完整静态分析行为以及如何通过reveal_type断言与# error诊断注释验证这些行为。ty 与 mdtest以 Markdown 为载体的类型检查器测试体系ty是 Ruff 仓库中正在开发的静态类型检查器位于 crates/ty_python_semantic用于在 Python 代码上进行类型推断与诊断。为了精确描述类型检查器在什么代码上产生什么结果仓库采用了一套名为mdtest的 Markdown 测试框架实现位于 crates/mdtest其核心思想是每个 Markdown 文件是一套测试套件标题下的每个##小节就是一个独立测试用例以py围栏包裹的代码块会被拼接成一个待检查的 Python 文件代码中的行内注释即为断言# revealed: ...断言某表达式的揭示类型# error: [...]断言该行产生指定代码的诊断测试运行器会将实际产生的类型与诊断和注释逐一比对匹配逻辑见 crates/mdtest/src/matcher.rs。本文主题文档 subscript/lists.md 正是这一体系下专门针对list 下标的行为规格说明。同目录下还有 instance.md自定义__getitem__/__setitem__、tuple.md元组下标等姊妹用例共同组成 subscript 主题族。运行该测试套件的入口脚本是 crates/ty_python_semantic/mdtest.py其声明了MDTEST_DIR CRATE_ROOT / resources / mdtest即本主题文档所在目录表明该目录下的 Markdown 文件会被真实执行并被 CI 持续校验因此其中的类型结论均有可复现依据。用数字与切片索引 list返回值类型推导文档首先确立了一个基础事实list 可以用数字或切片进行索引。对应的类型推断结果如下from typing import Any x [1, 2, 3] reveal_type(x) # revealed: list[int] reveal_type(x[0]) # revealed: int reveal_type(x[0:1]) # revealed: list[int]关键行为可以归纳为三点整数字面量索引x[0]返回的是 list 元素类型int。ty 在此处不做逐字面量精确到具体元素的推断这一点与 tuple 不同——tuple 的同类用例 tuple.md 中t[0]会揭示为Literal[1]因为 tuple 是异构的、元素类型固定的容器而 list 是均质容器元素类型统一为list[int]。切片索引x[0:1]返回的是list[int]即切片不会改变容器类型与元素类型只是产生一个新的 list。错误下标类型用字符串索引会产生[invalid-argument-type]诊断且表达式结果退化为Unknown# error: [invalid-argument-type] reveal_type(x[a]) # revealed: Unknown从同主题文档 instance.md 可以看出list 的下标最终会落到__getitem__方法调用上而[invalid-argument-type]正是__getitem__无法以该 key 调用时的诊断代码源码诊断定义参见 crates/ty_python_semantic/src/types/diagnostic.rs。切片边界的参数类型校验为什么float边界是非法的list 切片不仅要求整体语法合法还要求边界值类型正确。文档给出了正反两个用例def invalid_slice_bound(xs: list[int], start: float) - list[int]: return xs[start:] # error: [invalid-argument-type] def gradual_slice_bound(xs: list[int], start: Any) - list[int]: return xs[start:]start: float作为切片起点会触发[invalid-argument-type]因为 Python 的切片边界必须是整数或实现了__index__的类型float不满足而start: Any是合法的Any在类型检查中被视为渐进式gradual类型检查器不追究其具体值是否合规切片结果仍被接受为list[int]。这体现了 ty 在切片边界上的精确参数校验不是所有类型都能充当切片边界。结合 instance.md 中的对应用例integer_slices[invalid_bound:]报[invalid-argument-type]而slice[int | None, int | None, int | None]形式的注解则允许标准边界可以确认该规则对内置 list 和用户自定义__getitem__一视同仁。赋值语句中的下标检查invalid-assignment文档第二个主题是**下标赋值subscript assignment**中的类型检查。赋值场景同样会受到严格校验x [1, 2, 3] x[0 if (y : 2) else 1] 5 # error: [invalid-assignment] x[a if (y : 2) else 1] 6 # error: [invalid-assignment] x[a if (y : 2) else b] 6 def invalid_slice_bound(xs: list[int], start: float) - None: xs[start:] [] # error: [invalid-assignment] del xs[start:] # error: [invalid-argument-type]这里有两个值得注意的细节索引表达式本身可以是条件表达式含海象赋值。x[0 if (y : 2) else 1] 5合法因为该条件表达式无论走哪个分支结果都是整数索引一旦某个分支产生str如x[a if (y : 2) else 1] 6立即触发[invalid-assignment]。这说明 ty 会对下标索引的每个分支类型分别校验而不是笼统地放宽。写入与删除的校验代码不同xs[start:] []产生[invalid-assignment]——写入侧因切片边界类型非法而被拒绝del xs[start:]产生[invalid-argument-type]——删除操作走的是参数校验路径与赋值写入的诊断区分开。同类行为在 instance.md 中得到印证没有__setitem__的类做a[0] 0报 Cannot assign to a subscript...__setitem__收到非法 key 时报[invalid-assignment] Invalid subscript assignment with key of type ... and value of type ...。list 下标赋值本质上是调用 list 的__setitem__因此遵循同一套诊断体系。海象下标访问赋值后类型的可见性mdtest 文档还专门覆盖了一个较隐蔽的边界场景海象表达式:参与下标时揭示类型如何随绑定状态变化。三个用例层层递进用例一基本的海象下标xs: list[int | None] [1] xs[0] None reveal_type((xs : [1])[0]) # revealed: int | Nonexs声明为list[int | None]但海象赋值(xs : [1])重新绑定后的变量类型仍保留声明时的list[int | None]变量类型以声明为准因此[0]揭示为int | None。用例二海象下标后的再绑定不影响之前的揭示def f(xs: list[int | str]) - None: ys xs ys[0] s reveal_type((ys : [1])[0]) # revealed: intys起初继承xs的list[int | str]但海象赋值(ys : [1])将ys重新绑定为list[int]字面量于是(ys : [1])[0]揭示为int。注意此前的ys[0] s虽然把元素写成了str但静态类型检查并不会因为一次元素写入就修改变量整体的元素类型揭示结果依然来自海象表达式自身的类型。用例三海象下标赋值后的后期再绑定def f() - None: (ys : [1])[0] 2 ys [s] reveal_type(ys[0]) # revealed: str这里先执行海象下标赋值(ys : [1])[0] 2随后ys被重新绑定为[s]即list[str]因此后续ys[0]揭示为str。它验证了 ty 对赋值语句会更新变量当前绑定类型这一事实的追踪揭示结果取决于检查点处变量当时的绑定而非历史赋值。这三个用例共同说明 ty 在海象表达式与变量重绑定交互上的处理策略读取时以当前绑定为准且元素级写入不会污染容器元素的整体类型。如何复现与验证运行 mdtest 用例如果你希望亲自验证本文的全部行为可以直接在仓库中运行对应的 mdtest 测试。相关基础设施测试运行器源码crates/mdtest/src/lib.rs 与 crates/mdtest/src/matcher.rs驱动脚本crates/ty_python_semantic/mdtest.py其中MDTEST_SUITES明确把resources/mdtest目录作为测试套件加载Python 代码块的格式化与版本配置见 crates/ty_python_semantic/resources/mdtest/ruff.tomlline-length 130、以 stub 方式解析代码块、开启 formatter preview。典型的运行方式是在仓库根目录执行cargo test中与mdtest相关的测试或在安装了 Python 3.11 的环境下运行mdtest.py脚本并传入筛选器如 list subscripts。# revealed:断言与# error:断言由 matcher.rs 中的match_reveal_type_diagnostic与match_line逻辑执行严格比对断言必须与实际诊断一一对应多余或缺失都会被标记为测试失败。因此 lists.md 中的每一行揭示结果都是经过 CI 验证的事实规格。小结从 subscript/lists.md 这份规格文档可以提炼出 ty 对 list 下标的核心行为契约场景行为诊断代码list[int][0]整数索引揭示元素类型int无list[int][0:1]切片揭示list[int]无非法 key如字符串索引表达式类型退化为Unknowninvalid-argument-type非整数切片边界如float拒绝切片invalid-argument-type下标赋值时索引类型非法拒绝写入invalid-assignmentdel下标时索引类型非法拒绝删除invalid-argument-type海象下标读取以当前绑定类型为准无这些行为不仅适用于内置 list还通过统一的__getitem__/__setitem__方法调用模型延伸到所有可下标对象见 instance.md并与元组下标的精确字面量推断见 tuple.md形成互补。对类型检查器的使用者而言理解这套规则有助于预测 ty 在真实代码上对下标操作的报告对贡献者而言这份 Markdown 文档本身就是添加或修改 list 下标相关规则时的行为回归基准。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表