深入 Ruff 内置类型检查器 ty:Any动态类型与渐进类型系统的语义解析
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
Any是 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.md):
ty_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 <: Big:
from 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。
核心规则四:元组中的Any:tuple[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 RenamedAny),ty仍识别为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_assert+is_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),仅供参考