资讯动态

ty 泛型可调用对象全解析:PEP 484 传统语法下的类型推断与特化

发布时间:2026/9/10 8:35:12 来源:尧图企业网站定制
ty 泛型可调用对象全解析PEP 484 传统语法下的类型推断与特化【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff本篇技术指南以 Ruff 仓库内置的类型检查器tyRust 实现的 mdtest 规格文档为主体系统讲解传统Legacy即 PEP 484 风格语法下泛型可调用对象generic callable的完整语义从泛型函数、泛型类与可调用实例的泛型上下文识别到通过类型别名与函数返回值命名泛型Callable、重载回调参与泛型推断、ParamSpec 变参的渐进性处理以及多个高阶边界场景。读完本文你将掌握 ty 如何处理调用一个泛型 callable时的类型变量推断、约束求解、重载匹配顺序与特化保留规则并了解如何通过仓库内的 mdtest 用例复现与验证这些行为。本文主体内容来源于 crates/ty_python_semantic/resources/mdtest/generics/legacy/callables.md并结合 ty_extensions 内部 API 与 mdtest 测试框架 的源码进行印证。背景ty 类型检查器与 mdtest 规格测试ty 是 Ruff 仓库中的下一代 Python 类型检查器用 Rust 编写核心语义实现位于 crates/ty_python_semantic 与 crates/ty 等 crate。它的正确性由一个名为mdtest的测试体系保障Markdown 文档中的 Python 代码块会被抽取为待检查的源文件代码里的reveal_type(...)注释声明期望的类型# error: [...]声明期望的诊断而# revealed:声明期望的推断结果。运行时由 crates/ty_test/src/lib.rs 调用 crates/mdtest 解析文档结构逐段驱动类型检查并与期望值比对。这些 mdtest 文档按主题组织在 crates/ty_python_semantic/resources/mdtest 目录下其中 generics/legacy 专门覆盖 PEP 484 传统泛型语法TypeVar、ParamSpec、Generic等与 pep695PEP 695type/def f[T]语法形成对照。本文讨论的 callables.md 是其中关于可调用对象泛型行为的完整规格。代码块按 ruff.toml 配置以.pyi扩展名处理py pyi行长上限 130 列并启用格式化的 preview 模式。测试中大量使用了ty_extensions._internal里的测试辅助符号其声明位于 crates/ty_vendored/ty_extensions/_internal.pyigeneric_context(input: Any) - GenericContext | None返回一个类、函数、方法或类型别名的泛型上下文即它绑定的类型变量集合输入非泛型时返回Noneinto_regular_callable(value: Any) - Any把值强制转换为常规的Callable类型对应类型层面的RegularCallableTypeOfTypeOf、Unknown、static_assert、is_subtype_of等分别用于获取表达式推断类型、表示未知动态类型、断言与子类型检验。下文所有示例直接取自该规格文档# revealed:后即为 ty 实际推断并验证的结果。可调用对象可以是泛型的可调用并不局限于函数。泛型函数是最直观的例子函数签名中的TypeVar使它在每次调用时可以被特化ty 通过generic_context能观察到它绑定的类型变量。from typing import Callable, ParamSpec, TypeVar from ty_extensions._internal import generic_context P ParamSpec(P) T TypeVar(T) def identity(t: T) - T: return t # revealed: ty_extensions._internal.GenericContext[Tidentity] reveal_type(generic_context(identity)) # revealed: Literal[1] reveal_type(identity(1)) def identity2(c: Callable[P, T]) - Callable[P, T]: return c # revealed: ty_extensions._internal.GenericContext[Pidentity2, Tidentity2] reveal_type(generic_context(identity2)) # revealed: T - T reveal_type(identity2(identity)) class CallableInstance: def __call__(self, value: int, /) - str: return str(value) # revealed: (value: int, /) - str reveal_type(identity2(CallableInstance()))观察要点GenericContext[Tidentity]表示identity这个泛型对象绑定了类型变量T后缀是 ty 对类型变量所属对象的标识。identity(1)在调用点推断为Literal[1]说明类型变量按实参精确特化而非退化为int。identity2同时绑定PParamSpec与T两个变量。把identity泛型函数与CallableInstance()带__call__的可调用实例传给identity2均合法前者得到T - T的泛型 callable后者得到精确的(value: int, /) - str。泛型类同样适用——调用类即实例化它而实例化时其类型参数应当参与推断from typing import Generic class C(Generic[T]): def __init__(self, t: T) - None: ... # revealed: ty_extensions._internal.GenericContext[TC] reveal_type(generic_context(C)) # revealed: C[int] reveal_type(C(1))这里generic_context(C)揭示类对象C的泛型上下文为TC而C(1)调用则得到C[int]说明构造函数调用同样完成了类型参数的特化。强制转换为 Callable 时保留泛型性ty 提供into_regular_callable把任意可调用值规范化为常规Callable等价于类型层面的RegularCallableTypeOf。关键语义是转换后的 callable 不会丢失泛型信息——它仍然记得自己是泛型的继续参与调用点的类型变量推断。from ty_extensions._internal import into_regular_callable # revealed: T - T reveal_type(into_regular_callable(identity)) # revealed: ty_extensions._internal.GenericContext[Tidentity] reveal_type(generic_context(into_regular_callable(identity))) # revealed: Literal[1] reveal_type(into_regular_callable(identity)(1)) # revealed: **P, T - T) - ((**P) - T) reveal_type(into_regular_callable(identity2)) # revealed: ty_extensions._internal.GenericContext[Pidentity2, Tidentity2] reveal_type(generic_context(into_regular_callable(identity2))) # revealed: T - T reveal_type(into_regular_callable(identity2)(identity)) # revealed: T - C[T] reveal_type(into_regular_callable(C)) # revealed: ty_extensions._internal.GenericContext[TC] reveal_type(generic_context(into_regular_callable(C))) # revealed: C[int] reveal_type(into_regular_callable(C)(1))注意显示格式T - T中的方括号前缀即泛型 callable 的类型变量列表[**P, T]表明 ParamSpec 与 TypeVar 混合绑定。三个场景泛型函数、带 ParamSpec 的泛型函数、泛型类在转换后都能被generic_context再次识别并能在调用时继续特化说明规范化操作只改变调用风格如去除描述符语义不改变类型参数的开放性。命名泛型 Callable类型别名当需要反复引用某个泛型Callable形状时最直接的方式是类型别名。关键规则是别名中引用 TypeVar 的Callable本身是泛型的而使用该别名的函数不一定泛型。from typing import Callable, TypeVar from ty_extensions._internal import generic_context T TypeVar(T) IdentityCallable Callable[[T], T] def decorator_factory() - IdentityCallable[T]: def decorator(fn: T) - T: return fn # revealed: ty_extensions._internal.GenericContext[Tdecorator] reveal_type(generic_context(decorator)) # revealed: Literal[1] reveal_type(decorator(1)) return decorator # Note that decorator_factory returns a generic callable, but is not itself generic! # revealed: None reveal_type(generic_context(decorator_factory)) # revealed: Treturn - Treturn reveal_type(decorator_factory()) # revealed: ty_extensions._internal.GenericContext[Treturndecorator_factory] reveal_type(generic_context(decorator_factory())) # revealed: Literal[1] reveal_type(decorator_factory()(1))这段代码揭示了两个精细语义内部函数decorator是泛型的GenericContext[Tdecorator]但它不是工厂函数本身——generic_context(decorator_factory)返回None因为decorator_factory没有自己的类型参数。decorator_factory()的返回值是一个新的泛型 callable其类型变量被标注为Treturnty 用return后缀表示由返回类型创建的 fresh 类型变量且decorator_factory()(1)仍然能正确推断出Literal[1]。命名泛型 Callable 与 ParamSpec类型别名当 callable 形状中涉及 ParamSpec 时同样的模式成立——类型别名可以同时携带P与Tfrom typing import Callable, ParamSpec, TypeVar from ty_extensions._internal import generic_context P ParamSpec(P) T TypeVar(T) IdentityCallable Callable[[Callable[P, T]], Callable[P, T]] def decorator_factory() - IdentityCallable[P, T]: def decorator(fn: Callable[P, T]) - Callable[P, T]: return fn # revealed: ty_extensions._internal.GenericContext[Pdecorator, Tdecorator] reveal_type(generic_context(decorator)) return decorator # Note that decorator_factory returns a generic callable, but is not itself generic! # revealed: None reveal_type(generic_context(decorator_factory)) def identity(t: T) - T: return t # revealed: **Preturn, Treturn - Treturn, /) - ((**Preturn) - Treturn) reveal_type(decorator_factory()) # revealed: ty_extensions._internal.GenericContext[Preturndecorator_factory, Treturndecorator_factory] reveal_type(generic_context(decorator_factory())) # revealed: T - T reveal_type(decorator_factory()(identity)) # revealed: Literal[1] reveal_type(decorator_factory()(identity)(1))[**Preturn, Treturn]说明返回的 callable 同时携带返回值特化的 ParamSpec与 TypeVar把identity传入后ParamSpec 被具体化为identity的签名最终得到T - T再调用一次即得Literal[1]。整个链条说明 ParamSpec 特化是签名保真的参数列表的形状被完整传递。命名泛型 Callable函数返回值除了类型别名也可以直接在返回注解中写出泛型Callable。ty 的判定规则是如果某个 TypeVar 只出现在Callable内部且只出现在返回类型位置那么泛型的是那个被返回的 callable而不是函数本身。from typing import Callable, TypeVar from ty_extensions._internal import generic_context T TypeVar(T) def decorator_factory() - Callable[[T], T]: def decorator(fn: T) - T: return fn # revealed: ty_extensions._internal.GenericContext[Tdecorator] reveal_type(generic_context(decorator)) return decorator # Note that decorator_factory returns a generic callable, but is not itself generic! # revealed: None reveal_type(generic_context(decorator_factory)) # revealed: Treturn - Treturn reveal_type(decorator_factory()) # revealed: ty_extensions._internal.GenericContext[Treturndecorator_factory] reveal_type(generic_context(decorator_factory())) # revealed: Literal[1] reveal_type(decorator_factory()(1))与别名版本结果完全一致工厂不泛型None返回值是 fresh 类型变量Treturn的泛型 callable。反例TypeVar 同时出现在参数中时规则的另一半同样重要如果该 TypeVar 还出现在某个参数中那么泛型的是函数本身返回的Callable不再泛型。此时返回的 callable 已被参数特化def outside_callable(t: T) - Callable[[T], T]: raise NotImplementedError # revealed: ty_extensions._internal.GenericContext[Toutside_callable] reveal_type(generic_context(outside_callable)) # revealed: (int, /) - int reveal_type(outside_callable(1)) # revealed: None reveal_type(generic_context(outside_callable(1))) # error: [invalid-argument-type] outside_callable(1)(string)outside_callable(1)返回(int, /) - int——一个非泛型、参数已固化为int的 callable因此generic_context为None再向它传string必然触发invalid-argument-type诊断。这正是泛型归属判定的分水岭TypeVar 出现位置决定谁持有泛型身份。带 ParamSpec 的函数返回值ParamSpec 版本遵循完全相同的规则from typing import Callable, ParamSpec, TypeVar from ty_extensions._internal import generic_context P ParamSpec(P) T TypeVar(T) def decorator_factory() - Callable[[Callable[P, T]], Callable[P, T]]: def decorator(fn: Callable[P, T]) - Callable[P, T]: return fn # revealed: ty_extensions._internal.GenericContext[Pdecorator, Tdecorator] reveal_type(generic_context(decorator)) return decorator # Note that decorator_factory returns a generic callable, but is not itself generic! # revealed: None reveal_type(generic_context(decorator_factory)) def identity(t: T) - T: return t # revealed: **Preturn, Treturn - Treturn, /) - ((**Preturn) - Treturn) reveal_type(decorator_factory()) # revealed: ty_extensions._internal.GenericContext[Preturndecorator_factory, Treturndecorator_factory] reveal_type(generic_context(decorator_factory())) # revealed: T - T reveal_type(decorator_factory()(identity)) # revealed: Literal[1] reveal_type(decorator_factory()(identity)(1))一个值得注意的实现细节是传统语法工厂的 return 语句是按照其返回类型的词法形式lexical form来检查的即使返回的 callable 接受并返回另一个 callable 也是如此from typing import NoReturn class WrappedCallable: def __call__(self, *args: object, **kwargs: object) - NoReturn: raise NotImplementedError def nested_callable_factory() - Callable[[Callable[P, T]], Callable[P, T]]: return lambda callback: WrappedCallable()这里lambda返回WrappedCallable一个NoReturn可调用实例ty 将其与Callable[[Callable[P, T]], Callable[P, T]]的词法形式比对验证了返回注解按字面形状约束 return 语句的检查策略。反例ParamSpec 同时出现在参数中同样地ParamSpec 一旦出现在参数里泛型身份就归属函数def outside_callable(func: Callable[P, T]) - Callable[P, T]: raise NotImplementedError # revealed: ty_extensions._internal.GenericContext[Poutside_callable, Toutside_callable] reveal_type(generic_context(outside_callable)) def int_identity(x: int) - int: return x # revealed: (x: int) - int reveal_type(outside_callable(int_identity)) # revealed: None reveal_type(generic_context(outside_callable(int_identity))) # error: [invalid-argument-type] outside_callable(int_identity)(string)outside_callable绑定Poutside_callable与Toutside_callable传入int_identity后返回(x: int) - int不再泛型因此generic_context为None后续错误调用触发诊断。重载可调用对象作为泛型 Callable 参数当一个重载overload函数被传给泛型Callable参数时ty 要求整个重载集合作为一个整体与目标 callable 兼容。类型变量的推断只从第一个匹配的重载进行而不是把各重载的参数类型取并集——后者会对逆变contravariant类型变量产生无法满足的期望类型。from typing import Callable, TypeVar, overload T TypeVar(T) def accepts_callable(converter: Callable[[T], None]) - T: raise NotImplementedError overload def f(val: str) - None: ... overload def f(val: bytes) - None: ... def f(val: str | bytes) - None: pass reveal_type(accepts_callable(f)) # revealed: str | bytesf的每个重载都是(str) - None或(bytes) - None与Callable[[T], None]兼容ty 取第一个匹配重载的参数类型str与bytes的并集得到str | bytes——这正是重载集合整体兼容 从首个匹配重载推断两条规则的直接体现。被拒绝的重载回调保留有效特化重载回调中可能包含某个返回类型违反类型变量上界bound或声明约束constraints的备选分支。此时 ty 必须忽略无效分支、保留有效特化且结果与重载声明顺序无关from typing import Callable, TypeVar, overload Bounded TypeVar(Bounded, boundint) Constrained TypeVar(Constrained, int, bytes) overload def invalid_first(value: str) - str: ... overload def invalid_first(value: int) - int: ... def invalid_first(value: str | int) - str | int: return value overload def invalid_last(value: int) - int: ... overload def invalid_last(value: str) - str: ... def invalid_last(value: str | int) - str | int: return value def infer_bound(callback: Callable[..., Bounded]) - Bounded: raise NotImplementedError def infer_constrained(callback: Callable[..., Constrained]) - Constrained: raise NotImplementedError reveal_type(infer_bound(invalid_first)) # revealed: int reveal_type(infer_bound(invalid_last)) # revealed: int reveal_type(infer_constrained(invalid_first)) # revealed: int reveal_type(infer_constrained(invalid_last)) # revealed: intinfer_bound的返回类型Bounded有上界int因此str分支的返回类型str违反上界被排除int分支成为有效特化。invalid_first与invalid_last只是把无效分支放在首位或末位结果均为int——说明 ty 在求解约束时会扫描全部重载、剔除违反 bound/constraints 的备选而不是贪心地采用第一个分支。Constrainedint, bytes同理str不在约束集合中被排除后剩int。约束 TypeVar 的重载整体匹配 Callable[[T], T]当T被其它参数约束为某个联合时重载 callable 仍必须作为一个整体去满足Callable[[T], T]的形状from typing import Callable, TypeVar, overload T TypeVar(T) def apply_twice(converter: Callable[[T], T], left: T, right: T) - tuple[T, T]: return converter(left), converter(right) overload def f(val: int) - int: ... overload def f(val: str) - str: ... def f(val: int | str) - int | str: return val x: int | str 1 y: int | str a result apply_twice(f, x, y) # revealed: tuple[int | str, int | str] reveal_type(result)实参x、y都是int | str它们把T约束为int | str重载集合(int)-int与(str)-str整体上恰好与Callable[[int | str], int | str]的形状兼容联合中的每个成员都能匹配某个重载因此apply_twice返回tuple[int | str, int | str]。泛型工厂返回重载可调用对象泛型 callable 工厂返回的重载callable仍应可赋值给声明的泛型 callable 返回类型from collections.abc import Callable, Coroutine from typing import Any, TypeVar, overload S TypeVar(S) T TypeVar(T) U TypeVar(U) def singleton(flag: bool False) - Callable[[Callable[[int], S]], Callable[[int], S]]: overload def wrapper(func: Callable[[int], Coroutine[Any, Any, T]]) - Callable[[int], Coroutine[Any, Any, T]]: ... overload def wrapper(func: Callable[[int], U]) - Callable[[int], U]: ... def wrapper(func: Callable[[int], Coroutine[Any, Any, T] | U]) - Callable[[int], Coroutine[Any, Any, T] | U]: return func return wrapperwrapper拥有两个重载一个针对协程返回类型Coroutine[Any, Any, T]特化T一个兜底特化U。整个重载集合须整体满足Callable[[Callable[[int], S]], Callable[[int], S]]即返回的wrapper无论走哪个分支其参数与返回值同为接受int的 callable的形态都要成立。该用例没有显式reveal_type它的意义在于回归锁定工厂返回重载 callable 时不得产生赋值/匹配错误。部分注解重载的返回类型推断当重载集合中字面量专属分支没有注解返回类型、而兜底分支返回object时从整个回调推断返回类型应保留objectfrom typing import Callable, Literal, TypeVar, overload from typing_extensions import assert_type R TypeVar(R) T TypeVar(T) def infer_return(callback: Callable[[T], R]) - R: raise NotImplementedError overload def callback(value: Literal[a]): ... overload def callback(value: Literal[b]): ... overload def callback(value: Literal[c]): ... overload def callback(value: Literal[d, e]): ... overload def callback(value: Literal[f, g]): ... overload def callback(value: Literal[h, i]): ... overload def callback(value: Literal[j, k]): ... overload def callback(value: object) - object: ... def callback(value): raise NotImplementedError assert_type(infer_return(callback), object)前 8 个重载只注解了参数类型Literal[...]返回类型缺失最后一个(value: object) - object是兜底。ty 在推断R时取整个重载集合的返回类型证据未被注解分支不贡献返回信息于是R特化为兜底的object。assert_type(infer_return(callback), object)即断言这一点。泛型推断在投影预算耗尽后的恢复泛型推断存在投影projection预算限制。当一个元组参数中的每个元素各自匹配回调的一个重载、而组合起来的替代绑定超过投影极限时ty 无法继续求解会以Unknown恢复——并且default0这个精确实参不能补足缺失的回调证据参数顺序无论怎样都恢复为Unknownfrom typing import Callable, Literal, TypeVar, overload from typing_extensions import assert_type from ty_extensions._internal import Unknown R TypeVar(R) T TypeVar(T) U TypeVar(U) V TypeVar(V) def infer_return(callback: tuple[Callable[[T], R], Callable[[U], R], Callable[[V], R]], default: R) - R: raise NotImplementedError overload def callback(value: Literal[0, 1]): ... overload def callback(value: Literal[2, 3]): ... overload def callback(value: Literal[4, 5]): ... overload def callback(value: Literal[6, 7]): ... overload def callback(value: Literal[8, 9]): ... overload def callback(value: Literal[10, 11]): ... overload def callback(value: Literal[12, 13]): ... overload def callback(value: Literal[14, 15]): ... overload def callback(value: Literal[16, 17]): ... overload def callback(value: Literal[18, 19]): ... overload def callback(value: object) - object: ... def callback(value): raise NotImplementedError assert_type(infer_return((callback, callback, callback), 0), Unknown) assert_type(infer_return(default0, callback(callback, callback, callback)), Unknown)元组里三个位置各自独立匹配callback的重载集合每个匹配都产生替代绑定组合后超出 ty 泛型推断的投影预算R无法收敛到单一类型于是保守地返回Unknown见 crates/ty_vendored/ty_extensions/_internal.pyi 中对Unknown的定义缺少类型信息时推断出的动态类型。这个用例同时锁定预算耗尽 → 有序恢复为Unknown的行为防止未来实现退化为错误类型。高阶泛型可调用对象的多次出现fresh 类型变量同一个泛型 callable 在高阶调用中出现多次时每次出现都应获得独立的 fresh 类型变量from typing import Callable, TypeVar A TypeVar(A) B TypeVar(B) C TypeVar(C) X TypeVar(X) Y TypeVar(Y) def partial(c: Callable[[A, B], C], a: A) - Callable[[B], C]: def inner(b: B) - C: return c(a, b) return inner def drop(x: X, y: Y) - Y: return y # TODO: revealed: Literal[x] # We are correctly combining the constraint sets from both arguments of the outer # partial(partial, drop) call: one from passing partial as c, and one from passing drop as # a. However, we do that after having existentially quantified away the typevars from the generic # partial when its used as an argument, so this remains Unknown even after generic callable # occurrences are freshened. reveal_type(partial(partial, drop)(1)(x)) # revealed: Unknown # TODO: revealed: Literal[1] reveal_type(partial(partial, drop)(x)(1)) # revealed: Unknown外层partial(partial, drop)中内层partial作为第一个实参对应c、drop作为第二个实参对应a。ty 能够把两个参数产生的约束集合并一个来自partial传给c一个来自drop传给a但因为partial作为参数使用时其类型变量已被存在量化掉即使在 fresh 化之后结果仍是Unknown。文档中的TODO注释表明这属于已知改进项——它精确记录了约束集已合并正确但存在量化时机导致精度损失的现状是规格文档对已知局限的透明标注。ParamSpec 替换保留非渐进变参把变参类型特化为Any后再代入ParamSpec不应让参数列表变成渐进gradual的from typing import Any, Callable, Generic, ParamSpec, TypeVar from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_subtype_of P ParamSpec(P) T TypeVar(T) class C(Generic[T]): def method(self, *args: T, **kwargs: T) - None: ... def identity(callback: Callable[P, None]) - Callable[P, None]: return callback callback identity(C[Any]().method) reveal_type(callback) # revealed: (*args: Any, **kwargs: Any) - None static_assert(is_subtype_of(TypeOf[callback], Callable[[], None]))C[Any]().method的变参类型为*args: Any, **kwargs: Any。把该 callable 代入ParamSpec时即便特化为Any得到的签名仍然是确定性的(*args: Any, **kwargs: Any) - None——它依然是非渐进的普通签名因此static_assert(is_subtype_of(TypeOf[callback], Callable[[], None]))成立无参数调用合法。此用例锁定的是ParamSpec 替换不改变变参是否渐进这一属性。ParamSpec 推断保留非渐进残差参数去掉Concatenate前缀来推断ParamSpec时同样要保留剩余参数的渐进性from typing import Any, Callable, Concatenate, Generic, ParamSpec, TypeVar from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_subtype_of P ParamSpec(P) T TypeVar(T) class C(Generic[T]): def method(self, first: int, *args: T, **kwargs: T) - None: ... def strip_first(callback: Callable[Concatenate[int, P], None]) - Callable[P, None]: raise NotImplementedError callback strip_first(C[Any]().method) reveal_type(callback) # revealed: (*args: Any, **kwargs: Any) - None static_assert(is_subtype_of(TypeOf[callback], Callable[[], None]))method的签名是(first: int, *args: T, **kwargs: T) - Nonestrip_first用Concatenate[int, P]切掉首参first: int剩余部分推断为P。特化T Any后剩余参数仍是(*args: Any, **kwargs: Any)是非渐进的普通变参故子类型断言通过。两个 ParamSpec 用例共同说明无论替换还是推断ty 都精确跟踪变参的渐进性标记不会因Any特化或前缀切除而丢失。SymPy 回归测试重载/协议形状的崩溃锁定文档末尾是一个多文件的回归用例源于 SymPy 的一个重载/协议形状曾在 ty 的 overload-可赋值性路径上触发 panic因此被缩小为 MRE 作回归锁from __future__ import annotations from sympy.polys.compatibility import Domain, IPolys from typing import Generic, TypeVar, overload T TypeVar(T) class DefaultPrinting: pass class PolyRing(DefaultPrinting, IPolys[T], Generic[T]): symbols: tuple[object, ...] domain: Domain[T] def clone( self, symbols: object | None None, domain: object | None None, order: object | None None, ) - PolyRing[T]: return self overload def __getitem__(self, key: int) - PolyRing[T]: ... overload def __getitem__(self, key: slice) - PolyRing[T] | Domain[T]: ... def __getitem__(self, key: slice | int) - PolyRing[T] | Domain[T]: symbols self.symbols[key] if not symbols: return self.domain return self.clone(symbolssymbols) def takes_ring(x: PolyRing[int]) - None: reveal_type(x[0]) # revealed: PolyRing[int] reveal_type(x[:]) # revealed: PolyRing[int] | Domain[int]配套的桩文件sympy/polys/compatibility.pyi等价物定义了Domain(Generic[T])与带重载clone/__getitem__的IPolys(Protocol[T])from __future__ import annotations from typing import Generic, Protocol, TypeVar, overload T TypeVar(T) S TypeVar(S) class Domain(Generic[T]): ... class IPolys(Protocol[T]): overload def clone( self, symbols: object | None None, domain: None None, order: None None, ) - IPolys[T]: ... overload def clone( self, symbols: object | None None, *, domain: Domain[S], order: None None, ) - IPolys[S]: ... overload def __getitem__(self, key: int) - IPolys[T]: ... overload def __getitem__(self, key: slice) - IPolys[T] | Domain[T]: ...PolyRing同时继承DefaultPrinting、IPolys[T]与Generic[T]并重载__getitem__。ty 需要在类同时满足协议重载集合的复杂形状下正确推断x[0]key: int命中第一个重载得PolyRing[int]x[:]key: slice命中第二个重载得PolyRing[int] | Domain[int]。这个用例验证了 overload-可赋值性路径在真实生态形状下的稳定性是本文讨论的所有规则在一个复杂组合场景中的综合应用。如何在本地复现这些测试这些文档全部是可运行的规格测试无需任何手动断言检查文档格式markdown 代码块中的 Python 片段会被抽取为源文件按 ruff.toml 配置以.pyi处理# revealed:与# error:注释即期望结果# snapshot:则关联文档末尾的snapshot代码块做精确诊断快照比对。运行 mdtest 套件在仓库根目录执行cargo test -p ty_python_semantic --test mdtest若要限定范围可加测试名过滤例如针对generics/legacy目录的用例。配置分层单个文档可用 TOML 块覆盖全局配置例如[environment] python-version 3.12配置会按章节层级继承与覆盖详见 mdtest_config.md。更细的运行入口在 crates/ty_test/src/lib.rsmdtest::run驱动与 crates/mdtest/src/parser.rs文档解析mdtest 的 Python 包装脚本为 crates/ty_python_semantic/mdtest.py外部依赖用例如本文 SymPy 场景所属类别还需通过MDTEST_EXTERNAL1等环境变量启用虚拟环境。总结传统语法下泛型 callable 的核心规则围绕 callables.md 这份规格可以提炼出 ty 对传统语法泛型可调用对象的完整语义泛型身份归属泛型函数、泛型类与可调用实例都可携带GenericContextTypeVar 仅出现在返回值的Callable内时泛型的是被返回的 callablefresh 变量记为Treturn一旦 TypeVar 出现在参数中泛型身份归属函数本身。转换不丢泛型into_regular_callable以及类型层面的RegularCallableTypeOf规范化调用风格但保留泛型性与特化能力。命名与返回类型别名与函数返回注解都能表达泛型CallableParamSpec 版本同样成立且返回语句按返回类型的词法形式检查。重载参与推断重载集合整体匹配目标Callable类型变量从首个匹配重载推断违反 bound/constraints 的分支被剔除且与声明顺序无关重载返回类型证据用于推断返回类型变量。资源与精度边界投影预算耗尽时有序恢复为Unknown高阶多次出现会 fresh 化类型变量但存在量化时机仍可能损失精度文档以TODO明示Any特化与Concatenate切除均不改变变参的渐进性。回归保障真实生态SymPy的重载/协议形状被收编为多文件 MRE防止 overload-可赋值性路径回归或崩溃。这些规则通过reveal_type、generic_context、static_assert与assert_type的组合被精确锁定读者可以随时在仓库中通过cargo test -p ty_python_semantic --test mdtest复现验证。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价