深入 ty 类型系统:Ruff 类型检查器 Type Compendium(类型事实表)全景解读
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
导读
本文以 Ruff 仓库内 ty 类型检查器的type_compendium(类型事实表)为核心,系统讲解 ty 对 Python 类型系统中重要、有趣且特殊的类型——Never、object、None、整数Literal、tuple、Any、AlwaysTruthy/AlwaysFalsy以及补类型~T——的建模规则与判定逻辑。阅读本文后,你将掌握 ty 在子类型、可赋值性、不相交性、并集/交集归约、类型收窄、真值性等核心关系上的精确语义,并能理解这些事实表文档本身如何作为可运行的 mdtest 测试来约束实现与规范保持一致。
Type Compendium 是什么:一份"活的"类型系统参考手册
type_compendium位于 crates/ty_python_semantic/resources/mdtest/type_compendium/README.md,其中包含一系列关于"重要、有趣且特殊"的类型的"事实表"(fact sheets)。它承担两个职责:
- 教育性参考:面向 ty 的开发者与用户,以易于理解的方式说明 ty 对 Python 类型系统各类型的解释;
- 活文档(living document):这些文档同时是 mdtest 测试——文档中的每一段 Python 代码都会被真实执行与校验,确保 ty 对类型及其属性的实现与规范保持一致。一旦实现行为与文档断言不符,测试即失败。
从仓库结构看,mdtest 机制由 crates/mdtest 与 crates/ruff_mdtest 提供支持,因此这些文档不只是"读"的,更是"跑"的。
事实表目录(对应仓库文件):
NeverobjectNone- 整数
Literal tuple类型AnyAlwaysTruthy、AlwaysFalsy~T
文档中大量使用两个关键验证手段:static_assert(来自ty_extensions,在编译期断言某类型关系成立)以及reveal_type/assert_type(揭示或断言某个表达式在 ty 眼中的静态类型)。这些断言就是测试用例本身。
Never:空集与底类型
Never是每个类型的子类型
Never表示值的空集,是 Python 类型系统中的底类型(bottom type):它是每个类型的子类型,但除Never自身以及上界为Never的类型变量外,没有任何类型是Never的子类型。见 never.md:
from ty_extensions import static_assert from ty_extensions._internal import is_subtype_of from typing_extensions import Never, TypeVar class C: ... static_assert(is_subtype_of(Never, int)) static_assert(is_subtype_of(Never, object)) static_assert(is_subtype_of(Never, C)) static_assert(is_subtype_of(Never, Never)) static_assert(not is_subtype_of(int, Never)) T = TypeVar("T", bound=Never) def _(t: T): static_assert(is_subtype_of(T, Never))Never可赋值给每个类型
Never可赋值给任何类型,这一事实在"需要特定类型值的上下文中调用错误处理函数"时非常有用。例如把raise_error的返回类型从Never改成None会导致类型错误:
from ty_extensions import static_assert from ty_extensions._internal import is_assignable_to from typing_extensions import Never, Any static_assert(is_assignable_to(Never, int)) static_assert(is_assignable_to(Never, object)) static_assert(is_assignable_to(Never, Any)) static_assert(is_assignable_to(Never, Never)) def raise_error() -> Never: raise Exception("...") def f(divisor: int) -> None: x: float = (1 / divisor) if divisor != 0 else raise_error()Never在注解中的两种用法
返回值位置:表示函数永不返回控制权——总是抛异常、调用sys.exit()、进入死循环或无限递归:
from typing_extensions import Never def raises_unconditionally() -> Never: raise Exception("This function always raises an exception") def exits_unconditionally() -> Never: import sys return sys.exit(1) def loops_forever() -> Never: while True: pass def recursive_never() -> Never: return recursive_never()参数位置:表示该函数永远不可能被调用,因为永远无法传入一个Never类型的值(这样的值不存在):
def can_not_be_called(n: Never) -> int: return 0与其他类型的关系:不相交、并集与交集
两个类型不相交(disjoint)当且仅当它们的交集为空。由于Never没有成员,它与所有类型都不相交(包括自身)。同时:
- 并集:
Never总是可以从并集中移除,即P | Never | Q | None等价于P | Q | None; - 交集:与
Never求交集结果恒为Never,即P & Never & Q等价于Never。
from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to from typing_extensions import Never class P: ... class Q: ... static_assert(is_equivalent_to(P | Never | Q | None, P | Q | None)) static_assert(is_equivalent_to(P & Never & Q, Never))Never与object的互补性
object描述所有可能值的集合,Never描述空集,二者互为补集:~object等价于Never,~Never等价于object。这种对偶性贯穿始终:
| 关系 | Never | object |
|---|---|---|
| 子类型 | 是每个类型的子类型 | 是每个类型的超类型 |
| 可赋值性 | 可赋值给每个类型 | 可由每个类型赋值 |
| 不相交性 | 与每个类型不相交 | 与每个类型重叠 |
| 并集 | 构建并集是 no-op | 与object求并集得object |
| 交集 | 求交集得Never | 求交集是 no-op |
list[Never]与含Never的元组
list[Never]是一个合理的类型,并不等价于Never——空列表即为其成员:x: list[Never] = []。
而tuple[int, Never]同样区别于Never:由于元组注解可以描述用户自定义子类,其元素类型始终保留在类型中。同质元组tuple[Never, ...]也与Never不同,因为空元组是它的成员:
static_assert(not is_equivalent_to(tuple[int, Never], Never)) static_assert(not is_equivalent_to(tuple[Never, ...], Never)) t: tuple[Never, ...] = ()NoReturn与Never相同
NoReturn只是Never的另一个名字:
from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to from typing_extensions import NoReturn, Never static_assert(is_equivalent_to(NoReturn, Never))object:顶类型
object表示所有 Python 对象的集合,是类型系统中的顶类型(top type)。见 object.md。
超类型与可赋值性
所有类型都是object的子类型,一切值也都可以赋给object。这可以用来构造异构对象集合(但也会抹掉更具体的类型信息):
from ty_extensions import static_assert from ty_extensions._internal import is_subtype_of, is_assignable_to from typing_extensions import Any, Never static_assert(is_subtype_of(int, object)) static_assert(is_subtype_of(str, object)) static_assert(is_subtype_of(type, object)) static_assert(is_assignable_to(int, object)) static_assert(is_assignable_to(str | bytes, object)) static_assert(is_assignable_to(Never, object)) static_assert(is_assignable_to(Any, object)) x: list[object] = [1, "a", ()]重叠性、并集与交集
除Never外,没有哪个类型与object不相交;int、str、type、Any都与object重叠,只有Never与object不相交。
- 与
object求并集:结果恒为object,即int | object | None等价于object; - 与
object求交集:结果等价于原类型,即P & object & Q等价于P & Q。
None:单例类型
None(或NoneType,见下文)是只有一个成员——对象None——的单例类型。见 none.md。
单例类型的子类型
与其他单例类型一样,None仅有的子类型是None自身与Never:
static_assert(is_subtype_of(None, None)) static_assert(is_subtype_of(Never, None))Optional[T]只是T | None的别名
from typing import Optional, Union from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to class T: ... static_assert(is_equivalent_to(Optional[T], T | None)) static_assert(is_equivalent_to(Optional[T], Union[T, None]))基于is/is not的类型收窄
与其他单例类型一样,ty 支持通过is None/is not None进行类型收窄:
from typing_extensions import assert_type class T: ... def f(x: T | None): if x is None: assert_type(x, None) else: assert_type(x, T) assert_type(x, T | None) if x is not None: assert_type(x, T) else: assert_type(x, None)NoneType与None等价
None的特殊之处在于运行时实例的名字也可以作为类型使用:对象None是类型None的实例。需要区分二者时,可使用自 Python 3.10 起可用的拼写NoneType,它与None等价:
[environment] python-version = "3.10"from types import NoneType from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to static_assert(is_equivalent_to(NoneType, None))整数Literal类型:值与对象身份的分离
整数字面量类型表示具有某个特定值的所有整数对象的集合,例如Literal[54165]。见 integer_literals.md。
整数Literal不是单例类型
一个整数类型不一定是单例类型:可能有多个运行时对象(位于不同内存地址)表示同一个整数值。如下代码可能打印False:
x = 54165 y = 54165 print(x is y)在实践中,CPython 3.13.0 下以脚本方式执行会打印True,但在 REPL 中会打印False。由于这是运行时实现细节,ty 将所有整数字面量类型建模为非单例类型:
from ty_extensions import static_assert from ty_extensions._internal import is_singleton from typing import Literal static_assert(not is_singleton(Literal[0])) static_assert(not is_singleton(Literal[1])) static_assert(not is_singleton(Literal[54165]))这直接影响类型收窄:不能通过is not判断变量不是某个整数字面量类型(虽然这本来也不是推荐做法):
def f(x: int): if x is 54165: # 若 x 与左侧字面量是同一对象,则值必然相同 reveal_type(x) # revealed: Literal[54165] if x is not 54165: # 无法收窄为 int & ~Literal[54165],因为 x 可能值相同但对象身份不同 reveal_type(x) # revealed: int相等性收窄
整数Literal可以在相等比较中收窄类型:
def f(x: int): if x == 54165: # 默认情况下,ty 假定宽泛的 int 使用内建相等性; # 开启 strict-equality-semantics 会禁用此收窄 reveal_type(x) # revealed: Literal[54165] if x != 54165: reveal_type(x) # revealed: int & ~Literal[54165]子类型关系
- 整数
Literal都是int的子类型;但int不等价于所有整数字面量的并集,因为True/False是int的成员却不是任何整数字面量类型的成员:Literal[True]不是Literal[1]的子类型,Literal[False]也不是Literal[0]的子类型。 int可被继承,子类实例同样是int的子类型(CustomInt(int)是int的子类型)。- 整数
Literal不是float/complex的子类型,但 typing 规范对float与complex有特殊规定——接受整数(因此也接受整数字面量)的地方可以使用float/complex。ty 为此把注解float识别为int | float,并暴露JustFloat、JustComplex两个内部类型用于测试:
from ty_extensions import static_assert, JustFloat, JustComplex from ty_extensions._internal import is_subtype_of from typing import Literal static_assert(not is_subtype_of(Literal[0], JustFloat) and not is_subtype_of(Literal[0], JustComplex)) static_assert(not is_subtype_of(Literal[54165], JustFloat) and not is_subtype_of(Literal[54165], JustComplex)) a: JustFloat = 1 # error: [invalid-assignment] b: JustComplex = 1 # error: [invalid-assignment] x: float = 1 y: complex = 1- 一个整数字面量类型能被命名的子类型只有它自身与
Never。
不相交性
两个整数字面量类型Literal[a]与Literal[b]在a != b时不相交。
整数字面量算术:把数学写进类型系统
ty 支持在整数字面量类型上进行一系列算术运算,甚至能静态验证 (3, 4, 5) 是勾股三元组:
from ty_extensions import static_assert static_assert(3**2 + 4**2 == 5**2)借助整数字面量的并集,还可以在有限定义域上解方程(判断解是否存在):
from typing import Literal, assert_type type Nat = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] def pythagorean_triples(a: Nat, b: Nat, c: Nat): # 答案是 bool,因为确实存在解(3² + 4² = 5²) assert_type(a**2 + b**2 == c**2, bool) def fermats_last_theorem(a: Nat, b: Nat, c: Nat): # 答案是 Literal[False],因为不存在解 assert_type(a**3 + b**3 == c**3, Literal[False])真值性
除0恒为假外,整数字面量恒为真。这可用于类型收窄:
def f(x: Literal[0, 1, 54365]): if x: assert_type(x, Literal[1, 54365]) else: assert_type(x, Literal[0])tuple类型:乘积类型与丰富的结构语义
元组是 ty 类型系统中语义最丰富的部分之一,见 tuple.md。
元组作为乘积类型
tuple[P, Q]的成员是有序对(p, q)(p是P的成员,q是Q的成员),类比集合的笛卡尔积:
class P: ... class Q: ... def _(p: P, q: Q): assert_type((p, q), tuple[P, Q])实例化tuple
像所有类一样,调用tuple类可以实例化元组;实例化特化版本时会校验传入值是否匹配元素类型:
from typing_extensions import Iterable, Never reveal_type(tuple()) # revealed: tuple[()] reveal_type(tupleint)) # revealed: tuple[int] reveal_type(tuple[int, *tuple[str, ...]]((1,))) # revealed: tuple[int, *tuple[str, ...]] reveal_type(().__class__()) # revealed: tuple[()] reveal_type((1, 2).__class__((1, 2))) # revealed: tuple[Literal[1], Literal[2]]行为细节示例(同一文档中验证):
tuple(x)(x 为Iterable[int])→tuple[int, ...];tuple(z)(z 为Never)→tuple[Unknown, ...];tuple(aa)(aa 为list[Never])→tuple[()]——因为list[Never]的成员只能是空列表;tuple((1, 2))→tuple[Literal[1], Literal[2]];tuple([1])→tuple[int, ...];tupleint、tupleint, str)分别报[invalid-argument-type];- 缺失必选参数
iterable时(如(1,).__class__())报[missing-argument]。
实例化元组子类
元组子类继承其元组父类特化版本的构造器,包括可变参数元组子类(如tuple[Any, ...])、空元组子类(tuple[()])、单元素元组子类(tuple[int])、同质元组子类(tuple[int, ...])以及混合元组子类(tuple[int, *tuple[str, ...]])。空元组子类传入非空实参、混合子类元素类型不匹配、缺失实参等都会触发对应错误([invalid-argument-type]/[missing-argument])。
元组实例的元类型
tuple[str, int]的成员不仅是tuple的精确实例,还包括tuple[str, int]的任何子类的实例。因此tuple[str, int]的元类型应是type[tuple[str, int]]而非<class 'tuple[str, int]'>——后者会错误地暗示所有实例的__class__都是运行时对象tuple:
def f(x: tuple[int, ...], y: tuple[str, str], z: tuple[int, *tuple[str, ...], bytes]): reveal_type(type(x)) # revealed: type[tuple[int, ...]] reveal_type(type(y)) # revealed: type[tuple[str, str]] reveal_type(type(z)) # revealed: type[tuple[int, *tuple[str, ...], bytes]]子类型关系
tuple[S1, S2]是tuple[T1, T2]的子类型,当且仅当S1 <: T1且S2 <: T2(依此类推其他长度);不同长度的元组之间不存在子类型关系:
static_assert(is_subtype_of(tuple[S1], tuple[T1])) static_assert(not is_subtype_of(tuple[T1], tuple[S1])) static_assert(is_subtype_of(tuple[S1, S2], tuple[T1, T2])) static_assert(not is_subtype_of(tuple[T1, S2], tuple[S1, T2])) static_assert(not is_subtype_of(tuple[S1], tuple[T1, T2]))空元组tuple[()]
空元组的类型写作tuple[()],它不是单例类型(不同()实例不保证是同一对象,尽管 CPython 当前如此),并且可以被继承:
static_assert(not is_singleton(tuple[()])) class AnotherEmptyTuple(tuple[()]): ... static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()])) static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()])) static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()]))同理,非空元组也不是单例类型——即便其所有元素都是单例(如tuple[None]不是单例)。
含Never的元组
元组注解可以描述用户自定义子类,因此含必选Never元素的元组保留其形状而不化简为Never:
static_assert(not is_equivalent_to(tuple[Never], Never)) static_assert(not is_equivalent_to(tuple[int, Never], Never)) static_assert(not is_equivalent_to(tuple[Never, *tuple[int, ...]], Never))元组表达式同样保留元素类型:
def tuple_from_never(value: Never) -> None: reveal_type((value,)) # revealed: tuple[Never] reveal_type((1, value)) # revealed: tuple[Literal[1], Never]但可变长度部分是Never时,该部分恒为空,元组实际不再是变长的:
static_assert(is_equivalent_to(tuple[Never, ...], tuple[()])) static_assert(is_equivalent_to(tuple[int, *tuple[Never, ...]], tuple[int])) static_assert(is_equivalent_to(tuple[int, *tuple[Never, ...], int], tuple[int, int]))同质非空元组:用混合元组表达最小长度
同质元组tuple[int, ...]表示零个或多个元素;需要表达"一个或多个""两个或多个"时使用混合元组,且必选元素也可以出现在后缀:
def takes_zero_or_more(t: tuple[int, ...]) -> None: ... def takes_one_or_more(t: tuple[int, *tuple[int, ...]]) -> None: ... def takes_two_or_more(t: tuple[int, int, *tuple[int, ...]]) -> None: ... takes_one_or_more(()) # error: [invalid-argument-type] takes_one_or_more((1,))无论必选元素在前缀还是后缀,元组类型彼此等价(前后缀与变长部分的类型等价即可,不必完全相同):
static_assert(is_equivalent_to(tuple[int, *tuple[int, ...]], tuple[*tuple[int, ...], int])) static_assert(is_equivalent_to(tuple[int, int, *tuple[int, ...]], tuple[*tuple[int, ...], int, int]))元组的不相交性
- 最小长度不相容的两个元组(一个的最小长度大于另一个的最大长度)恒不相交;
- 必含元素逐一比对:
tuple[P1, P2]与tuple[Q1, Q2]只要P1与Q1或P2与Q2不相交即不相交(用@final类可验证); - 变长部分永远不会导致不相交(所有变长元组类型都包含空元组);
- 元组类型与任意实例类型不建模为不相交,因为
tuple允许被继承;但异构元组之间建模为不相交,为此显式禁止两个不同特化的异构元组共存于同一 MRO(否则报[invalid-generic-class])。
元组的真值性
- 空元组的真值恒为
False:bool(())的类型是Literal[False],tuple[()]可赋值给AlwaysFalsy; - 非空元组真值恒为
True——即使所有元素为假、甚至元素是渐进类型,因为元组真值只取决于长度:bool((False,))的类型是Literal[True]; - 变长元组的真值有歧义(既包含空元组又包含非空元组),既不可赋值给
AlwaysFalsy也不可赋值给AlwaysTruthy; - 混合元组只要有一个必选元素就恒为真(
tuple[int, *tuple[Any, ...]]可赋值给AlwaysTruthy)。这些结论与"元组可被继承、子类可覆写__bool__/__len__"存在已知冲突(文档示例NotAlwaysTruthyTuple),属当前实现的已知权衡。
未特化元组与Literal提升
未特化的tuple等价于tuple[Any, ...]与tuple[Unknown, ...]。
对于未注解的大元组,ty 只在元素数 ≤64 时推断Literal类型;超过 64 个元素则提升元组内部的Literal类型(has_64_elements保留全部Literal,has_65_elements整体拓宽为int)。Final注解、显式类型注解(如tuple[Literal[1], Literal[2], *tuple[int, ...], ...])可避免该提升。限制按"由字面量展开引入的元素"计数,空展开不消耗预算;64 元素展开保留字面量,多加一个元素则整体拓宽。
元组展开的其他细节
- 元组中的星号展开保留类型:
(1, *range(3), 3)→tuple[Literal[1], *tuple[int, ...], Literal[3]]; - 由列表字面量展开构造元组时保留每个元素的类型与位置:
(*[1, "two"],)→tuple[Literal[1], Literal["two"]],解包后first/second分别是Literal[1]/Literal["two"]; - 嵌套展开(含海象运算符)仍保持各自容器的绑定;
- 无效展开只报告一次迭代错误,周围字面量元素保留位置;
- 展开含变长元组(含符号化
TypeVarTuple)时保留两侧固定元素。
转换到其他Sequence类型
对协变容器(如frozenset),理想行为是不把Literal提升为其实例超类型(提升反而引入更多误报):frozenset((1, 2, 3))→frozenset[Literal[1, 2, 3]]。但对不变容器(如list),Literal总是被提升:list((1, 2, 3))→list[int]。
Any:渐进类型系统的动态类型
Any是 Python 渐进类型系统中的动态类型,表示未知的静态类型,即一个未知的运行时值集合。见 any.md。
双向可赋值
每个类型都可赋值给Any,Any也可赋值给每个类型(含Never、object、type[Any]等):
static_assert(is_assignable_to(C, Any)) static_assert(is_assignable_to(Any, C)) static_assert(is_assignable_to(Never, Any)) static_assert(is_assignable_to(Any, Never)) static_assert(is_assignable_to(Any, Any))Any | T:带下界的未知静态类型
Any与全静态类型T的并集描述"至少与T一样大"的未知值集合,即下界为T的未知全静态类型:
class Big: ... class Medium(Big): ... class Small(Medium): ... static_assert(is_assignable_to(Any | Medium, Big)) static_assert(is_assignable_to(Any | Medium, Medium)) static_assert(not is_assignable_to(Any | Medium, Small))且Any | object等价于object(与所有含object的并集一致)。
Any & T:带上界的未知静态类型
Any与全静态类型T的交集描述"不大于T"的未知值集合,即上界为T的未知全静态类型:
static_assert(is_assignable_to(Small, Any & Medium)) static_assert(is_assignable_to(Medium, Any & Medium)) static_assert(not is_assignable_to(Big, Any & Medium))而Any & Never描述"不大于Never的未知全静态类型"——除Never自身外不存在这样的类型,因此Any & Never等价于Never。
含Any的元组:对规范"渐进类型"的直接验证
文档引用了 typing 规范 type system concepts 中关于渐进类型的论述:tuple[int, Any]不表示单一集合,而是一个有界范围的可能的取值集合;tuple[int, object]才是全静态类型。二者的差异可观察:tuple[int, Any]可赋值给tuple[int, int],而tuple[int, object]不行:
static_assert(is_assignable_to(tuple[int, Any], tuple[int, int])) static_assert(not is_assignable_to(tuple[int, object], tuple[int, int]))AlwaysTruthy与AlwaysFalsy:可静态推断的真值性
AlwaysTruthy/AlwaysFalsy描述"恒为真/恒为假"的值集合:at属于AlwaysTruthy当且仅当可静态推断bool(at)恒为True(类型为Literal[True]);af属于AlwaysFalsy当且仅当bool(af)恒为False。见 always_truthy_falsy.md。
典型成员
AlwaysTruthy:True、任意非零整数(1、123、-1)、非空字符串/字节串、__bool__返回Literal[True]的自定义类型实例;AlwaysFalsy:False、None、0、空字符串/字节串、__bool__返回Literal[False]的自定义类型实例。
不相交性与派生态
由定义直接可得AlwaysTruthy与AlwaysFalsy不相交。进一步定义:
Truthy = ~AlwaysFalsy(可能为真的值);Falsy = ~AlwaysTruthy(可能为假的值);AmbiguousTruthiness = Truthy & Falsy(既可能真也可能假的值,其交集非空)。
随机返回True/False的__bool__实现、可能返回空列表的maybe_empty_list()都属于歧义真值类别,bool()类型为bool。文档也标注了若干 TODO:目前把maybe_empty_list()赋给Truthy/Falsy/AmbiguousTruthiness会报[invalid-assignment],但按定义这些赋值应当成立。
子类型关系
只含恒真值的类型是AlwaysTruthy的子类型(Literal[True]、Literal[1]、Literal["non empty"]等);只含恒假值的类型是AlwaysFalsy的子类型(None、Literal[False]、Literal[0]、Literal[""]、Literal[False] | Literal[0]等);真值有歧义的类型两者都不是(bool、list[int]既不是AlwaysTruthy也不是AlwaysFalsy的子类型)。
开放问题与关联材料
tuple[()]是否恒为假目前仍在讨论中(对应 issue astral-sh/ruff#15528),当前实现将其建模为AlwaysFalsy的子类型。真值收窄相关的完整测试见 crates/ty_python_semantic/resources/mdtest/narrow/truthiness.md。
补类型~T:集合论中的"非"
~T是T的补类型,描述所有不在T中的值。见 not_t.md。
核心性质
- 与
T不相交:~T与T不相交,与T的子类型S也不相交; - 并集为全量:
T | ~T等价于object; - 反转子类型关系:若
S <: T,则~T <: ~S(类比逻辑否定反转<=的方向); - 反转可赋值关系:
S可赋值给T蕴含~T可赋值给~S;Any & S可赋值给Any & T蕴含~(Any & S)可赋值给~(Any & T); - 不相交蕴含子类型:若
P与Q不相交,则P <: ~Q且Q <: ~P(对@final类成立)。
德摩根定律
在集合论类型的语境下,德摩根定律成立:
static_assert(is_equivalent_to(~(P | Q), ~P & ~Q)) static_assert(is_equivalent_to(~(P & Q), ~P | ~Q))渐进类型的否定
Any表示未知值集合,~Any同样表示未知值集合,二者等价:
static_assert(is_equivalent_to(~Any, Any))这些事实表如何验证 ty 的实现
理解这些文档的验证机制,有助于把它们当作"可执行规范"使用:
static_assert断言:来自ty_extensions(ty 项目自带的测试辅助包,见 python/ruff 之外的 ty 相关资源),在 mdtest 执行阶段由 ty 检查器对断言中的类型关系进行求值,is_subtype_of、is_assignable_to、is_disjoint_from、is_equivalent_to、is_singleton等即类型关系的判定接口;reveal_type/assert_type:直接呈现 ty 对表达式推断出的静态类型,注释# revealed: ...中的内容即期望值,也是测试断言;[environment]TOML 块:指定该文档(或该代码块)的python-version(如"3.14"、"3.12"、"3.11"、"3.10"),说明部分语义(如NoneType可用性、Literal提升行为)依赖 Python 版本;- 错误注释:
# error: [invalid-assignment]、# error: [invalid-argument-type]、# error: [missing-argument]、# error: [not-iterable]、# error: [missing-type-argument]、# error: [invalid-generic-class]等标注期望触发的诊断代码,部分还带完整消息文本。
这些文档正是"规范与实现一致性"的守门员:任何一处行为偏差都会导致 mdtest 失败,从而推动 ty 的实现(位于 crates/ty_python_semantic/src)始终与文档描述一致。
小结
type_compendium用一组可执行的事实表,把 Python 类型系统中最微妙的部分——底类型Never与顶类型object的对偶、None与整数字面量的单例性差异、元组的乘积/变长/子类化语义、Any的渐进性、基于__bool__的真值性分类以及集合论补类型~T的代数性质——精确地固定下来。对 ty 用户而言,它是理解收窄行为与边界语义的权威参考;对 ty 开发者而言,它是约束实现、防止回归的活测试集。阅读时建议结合 crates/ty_python_semantic/src 的实现与 crates/ruff_mdtest 的测试框架,将文档断言与底层判定逻辑相互印证。
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考