ARTICLE DETAIL

资讯详情

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

深入 Ruff 内置类型检查器 ty:`Any` 动态类型与渐进类型系统的语义解析

深入 Ruff 内置类型检查器 ty:`Any` 动态类型与渐进类型系统的语义解析 深入 Ruff 内置类型检查器 tyAny动态类型与渐进类型系统的语义解析【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffAny是 Python 渐进类型系统gradual type system中的动态类型它不像object那样描述所有对象的集合而是描述一个未知的值集合。本文以 Ruff 仓库中ty类型检查器crates/ty_python_semantic的可执行规范文档 type_compendium/any.md 为主体系统讲解Any与object、Never、并集、交集、元组之间的类型语义并对照源码说明这些语义在类型检查器中的实现与验证方式。读完本文你将理解Any的双向可分配性、Any | T的下界语义、Any T的上界语义以及tuple[int, Any]与tuple[int, object]的本质区别并能读懂ty中以静态断言形式书写的类型系统测试。背景渐进类型系统与Any的定位在ty的渐进类型系统中Any被定义为一个动态类型它不代表某个具体的静态类型而代表一个未知的静态类型即一个未知的运行时值集合。这与object所有对象的静态上界Top、Never没有任何值的下界Bottom形成对照object描述的集合是已知且固定的全体 Python 对象Never描述的集合是已知且固定的空集Any描述的集合是未知的它可能对应任意某个具体的静态类型所描述的值集合。在源码层面ty内部正是把Any表示为动态类型枚举的一员。在 crates/ty_python_semantic/src/types.rs 中Type的构造器any()直接返回Type::Dynamic(DynamicType::Any)pub(crate) const fn any() - Self { Self::Dynamic(DynamicType::Any) }而DynamicType枚举中明确注释了Any的来源——types.rs/// An explicitly annotated typing.Any Any,与Any同族的动态类型还包括Unknown未标注注解时的未知类型见 type_compendium 相关的 ty_extensions 文档以及Todo等。值得注意的一点是types.rs 的is_spellable实现指出Unknown与Todo虽是非标准扩展但它们在语义上都精确等价于Any可以在用户注解中书写// Unknown and Todo are nonstandard extensions, // but they are both exactly equivalent to Any Type::Dynamic(_) true,可执行的类型语义mdtest、static_assert与类型谓词本文依据的核心文档位于 crates/ty_python_semantic/resources/mdtest/type_compendium/any.md它是ty的mdtest体系中的一份可执行规范文档中的 Python 代码片段会被当作真实测试运行其中标注的# revealed:、# error:、# snapshot:等注释都会被编译器逐条校验。该类文档的测试运行环境由文档开头的 TOML 配置指定[environment] python-version 3.14即本节内容均在仓库所支持的 Python 3.14 环境配置下验证。验证Any语义依赖三个核心工具其完整说明见 ty_extensions.mdty_extensions.static_assert(expr)在类型检查期强制断言某个表达式具有静态已知的真值。若参数求值为False或真值性无法静态确定则报出static-assert-error诊断断言失败时还会在诊断中显示Inferred type of argument is ...给出参数被推断出的类型。ty_extensions._internal.is_assignable_to(S, T)类型谓词判断类型S是否可以赋给类型T返回Literal[True]或Literal[False]。ty_extensions._internal.is_equivalent_to(S, T)判断两个类型是否等价同样返回字面量布尔类型。以is_assignable_to的基础用法为例同样出自 ty_extensions.md 的 Assignability 小节from ty_extensions import static_assert from ty_extensions._internal import is_assignable_to from typing import Any static_assert(is_assignable_to(int, Any)) static_assert(is_assignable_to(Any, str)) static_assert(not is_assignable_to(int, str))由于这些谓词返回的是Literal[True]/Literal[False]static_assert便能在编译期对任意类型关系进行断言构成一份可回归、可执行的类型系统行为规格。核心规则一Any的双向可分配性Any最显著的性质是每个类型都可以赋给Any同时Any也可以赋给每个类型。它既是所有类型的超类型也是所有类型的子类型这正是动态类型的含义——因为Any代表的未知集合可能恰好就是目标类型对应的集合也可能恰好是源类型对应的集合。在 any.md 中这一性质通过如下断言被固化from ty_extensions import static_assert from ty_extensions._internal import is_assignable_to from typing_extensions import Never, Any class C: ... static_assert(is_assignable_to(C, Any)) static_assert(is_assignable_to(Any, C)) static_assert(is_assignable_to(object, Any)) static_assert(is_assignable_to(Any, object)) static_assert(is_assignable_to(Never, Any)) static_assert(is_assignable_to(Any, Never)) static_assert(is_assignable_to(type, Any)) static_assert(is_assignable_to(Any, type)) static_assert(is_assignable_to(type[Any], Any)) static_assert(is_assignable_to(Any, type[Any]))逐条解读普通类C与Any双向可赋值object静态 Top与Any双向可赋值——注意这不意味着二者等价Any是未知集合object是全体对象集合二者的可赋值性来自Any的双向动态语义而等价是更强的概念Never静态 Bottom与Any双向可赋值类字面量类型type与Any、以及type[Any]与Any双向可赋值。此外与所有类型一样Any也具有自反性static_assert(is_assignable_to(Any, Any))在实现层面这种双向放行来自ty对Type::Dynamic(_)类型在处理赋值is_assignable_to时的特殊分支。is_assignable_to是Type上的核心关系查询被赋值检查types/diagnostic.rs 中的AssignmentDiagnosticKind、调用绑定types/call/bind.rs、约束求解types/constraints.rs等大量内部逻辑引用当任意一侧是动态类型时检查会直接放行从而在源码层面对应文档断言的行为。核心规则二与并集的交互Any | T—— 未知下界把Any与一个完全静态的类型T取并集得到Any | T。文档给出的语义是它描述了一个至少与T一样大的未知值集合即它代表一个下界为T的未知完全静态类型。用可赋值关系可以精确地演示这一点。考虑类层级Small : Medium : Bigfrom ty_extensions import static_assert from ty_extensions._internal import is_assignable_to, is_equivalent_to from typing_extensions import Any # A class hierarchy Small : Medium : Big class Big: ... class Medium(Big): ... class Small(Medium): ... static_assert(is_assignable_to(Any | Medium, Big)) static_assert(is_assignable_to(Any | Medium, Medium)) # Any | Medium is at least as large as Medium, so we cannot assign it to Small: static_assert(not is_assignable_to(Any | Medium, Small))由于Any | Medium代表的未知类型至少是Medium可能更大它可以赋给Medium及其超类Big但它不可能被收窄为Small因为未知集合可能取到Medium本身甚至更大的类型所以赋给Small无法被静态保证。一个值得注意的退化情形Any | object等价于object。事实上任何类型与object取并集都等价于object因为object已是静态全集此处只是借Any再次验证static_assert(is_equivalent_to(Any | object, object)) static_assert(is_equivalent_to(object | Any, object))也就是说当未知下界顶到静态上界object时未知性被吸收并集退化为一个确定的全集类型。关于ty中并集类型的整体处理可进一步阅读 union_types.md。核心规则三与交集的交互Any T—— 未知上界把Any与完全静态类型T取交集得到Any T。语义与并集对称它描述了一个不大于T的未知值集合即一个上界为T的未知完全静态类型。沿用同样的类层级from ty_extensions import static_assert from ty_extensions._internal import is_assignable_to, is_equivalent_to from typing import Any class Big: ... class Medium(Big): ... class Small(Medium): ... static_assert(is_assignable_to(Small, Any Medium)) static_assert(is_assignable_to(Medium, Any Medium))Small与Medium都可以赋给Any Medium因为该交集代表的未知类型最多是Medium而Small、Medium都满足这个上界约束。反过来static_assert(not is_assignable_to(Big, Any Medium))Big不能赋给Any Medium——Any Medium不可能比Medium更大因此不存在任何使交集大到容纳Big的实例化materialization方式。文档中对此的表述是不存在Any Medium的实例化能使其与Big一样大。交集还有一个重要的退化情形Any Never等价于Never。因为Never是唯一一个不大于Never的完全静态类型未知上界一旦收紧到Never未知集合就只能取空集本身from typing_extensions import Never static_assert(is_equivalent_to(Any Never, Never)) static_assert(is_equivalent_to(Never Any, Never))可见Any与静态类型的并/交运算恰好把未知性限制在了一个由静态类型T围出的区间内Any | T给出下界Any T给出上界。关于交集类型的构造与化简例如Intersection[int, bool]化简为bool、Intersection[int, Never]化简为Never可参阅 ty_extensions.md 的 Intersection 小节与 intersection_types.md。核心规则四元组中的Anytuple[int, Any]与tuple[int, object]Any嵌入复合类型后最能体现未知集合与已知集合差异的场景是元组。类型系统概念文档关于渐进类型的章节指出tuple[int, Any]并不代表所有首元素为整数的二元组这一单个集合——那是一个完全静态类型应该写作tuple[int, object]。相反tuple[int, Any]代表某个未知的二元组值集合它可能是所有二元整数组的集合可能是整数 字符串二元组的集合也可能是其它某个二元组集合。这一区别在实践中的可观测差异是可以把tuple[int, Any]类型的表达式赋给tuple[int, int]类型的目标而把tuple[int, object]赋给tuple[int, int]则是静态类型错误from ty_extensions import static_assert from ty_extensions._internal import is_assignable_to from typing import Any static_assert(is_assignable_to(tuple[int, Any], tuple[int, int])) static_assert(not is_assignable_to(tuple[int, object], tuple[int, int]))原因在于tuple[int, Any]的第二个元素是未知类型该未知集合可能恰好是int因此目标tuple[int, int]是它的一个可能实例化赋值被允许而tuple[int, object]的第二个元素是确定的全集object把全体对象的集合收窄为int在静态上是无法保证的因此报错。这解释了为何Any在复合类型内部与object行为截然不同——object是已知的大Any是未知而未知恰恰为赋值保留了可能性。源码印证Any在ty内部表示与书写规则除了Type::Dynamic(DynamicType::Any)这一核心表示外源码中还有几处与本文主题直接相关的实现细节类型定义回溯在 types.rs 中Type::definition会把内部的动态Any映射回用户可书写的特殊形式SpecialFormType::Any即运行时typing.Any对应的特殊形式保证诊断与reveal_type输出使用用户熟悉的拼写Self::Dynamic(DynamicType::Any) { Type::SpecialForm(SpecialFormType::Any).definition(db, env) }拼写识别typing.Any是命名Any类型的方式即使把它别名重命名如from typing import Any as RenamedAnyty仍识别为Any而用户自定义的class Any: ...则只是普通类不再是动态类型。这部分行为由 annotations/any.md 以reveal_type断言形式固化。允许子类化类型规范允许定义Any的子类。直接或间接子类的实例保留其名义类型与声明的成员reveal_type(SubclassOfAny())显示为SubclassOfAny其 MRO 中会插入Any如(class SubclassOfAny, Any, class object)。这与Unknown同样可被子类化的行为一致见 ty_extensions.md。物化materialization机制渐进类型系统在处理未知动态类型时会通过物化将其解析为静态上界或下界。源码注释中提到Top[list[Any]]与Bottom[list[Any]]这类带MaterializationKind的GenericAlias表示types.rs以Top物化的list[Any]是所有list[Any]实例化的上界Bottom物化则对应下界。这正是未知集合被静态类型围出区间这一直觉在实现中的体现——并集Any | T给出下界、交集Any T给出上界而物化负责在这些边界之间做静态化处理。总结Any是 Ruff 内置类型检查器ty渐进类型系统的基石它代表一个未知的静态类型因此与所有类型双向可赋值与静态类型取并集时表现为下界为T的未知类型Any | T取交集时表现为上界为T的未知类型Any T嵌入元组后tuple[int, Any]与tuple[int, object]展现出截然不同的可赋值行为——前者是可能的实例化后者是确定的大集合。这些语义并非停留在文档叙述层面而是被 type_compendium/any.md 中以static_assertis_assignable_to/is_equivalent_to的形式固化为可执行测试并在 types.rs 中以Dynamic(DynamicType::Any)为核心表示、配合特殊形式映射与物化机制落地。如果想继续深入可以对照阅读同目录下的 never.md、object.md以及类型属性测试 is_assignable_to.md 与 is_equivalent_to.md从而完整掌握ty对渐进类型语义的建模方式。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表