
Ruff ty 类型检查器中的typing.Any完整语义从基本用法到 Any 子类的渐进赋值机制【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/rufftyping.Any是 Python 类型系统中的特殊存在任何类型都可以赋值给它而它也可以赋值给任何类型。本文以 Ruff 仓库中 ty 类型检查器的规格测试文档 annotations/any.md 为主体逐条剖析 ty 对Any的建模与实现——包括拼写识别、别名与遮蔽、Any子类的单向渐进赋值、运行时属性以及isinstance/issubclass等非法用法。读完本文你将掌握 ty 中Any的完整行为边界并能看懂reveal_type、reveal_mro等调试指令的输出以及各类[invalid-*]诊断的触发条件。背景mdtest 规格文档与 ty 的对应关系本文讨论的文档位于crates/ty_python_semantic/resources/mdtest/annotations/any.md它是 ty 类型检查器Ruff 仓库中独立于 linter/formatter 的类型检查引擎源码位于 crates/ty_python_semantic的 mdtest 规格测试文件。这类文档采用文档即测试的形式每个代码块都会被实际执行其中形如reveal_type(x) # revealed: Any的注释是断言# error: [invalid-assignment]则是期望产生的诊断# snapshot用于断言完整的多行诊断输出。ty 对Any的核心建模分散在以下几处源码中后续小节会逐一引用special_form.rsSpecialFormType::Any是typing.Any这个特殊形式的类型表示例如Self::Any Some(Type::any())第 223 行表明在合法的类型表达式中Any特殊形式会被转换为动态类型Type::any()class_base.rsClassBase::Any与ClassBase::Dynamic(DynamicType::Any)两个变体分别表示显式继承自Any与基类表达式的类型是Any/Unknown这是本文子类章节的底层实现diagnostic.rsinvalid-assignment、invalid-type-form、call-non-callable、invalid-metaclass、invalid-argument-type等诊断的定义位置。Any的拼写与识别Annotationtyping.Any是命名Any类型的一种方式。最基本的用法是作为变量注解此时该变量可以被重新赋值为任意类型且在函数内部reveal_type始终揭示为Anyfrom typing import Any x: Any 1 x foo def f(): reveal_type(x) # revealed: Any关键点在于reveal_type揭示的是变量的声明类型而非当前值因此即使x被先后赋值为int和str其类型依然稳定地是Any。这体现了Any的动态本质——ty 不会对它做任何细化或跟踪。别名也生效Aliased to a different nameAny的类型身份绑定在符号上而非名字上。即使把typing.Any导入成别的名字ty 依然能识别它是Any的另一种拼写from typing import Any as RenamedAny x: RenamedAny 1 x foo def f(): reveal_type(x) # revealed: Any从实现上看这是通过在模块属性解析时把符号解析为SpecialFormType::Any见 special_form.rs 的try_from_file_and_name它会按导入路径匹配 special form再经由Self::Any Some(Type::any())第 223 行得到动态类型与变量名完全无关。名字遮蔽Shadowed class反过来如果用户在自己的模块里定义了一个名为Any的类那么类型表达式中出现的Any指向的是这个本地类而不是typing.Any。mdtest 用一个小技巧验证了这一点class Any: ... x: Any def f(): reveal_type(x) # revealed: Any # 由于 str 可以赋给 typing.Any但不可赋给本地定义的类 # 下面的赋值必须报错以此证明我们没有误用 typing.Any。 y: Any not an Any # error: [invalid-assignment]这里的reveal_type(x) # revealed: Any揭示的是本地类Any的实例类型诊断渲染时同名显示而y: Any not an Any会触发[invalid-assignment]因为str与本地空类Any不兼容。这正是 mdtest 断言机制的巧妙之处——通过反向验证排除实现中意外回退到typing.Any的可能。Any的子类渐进赋值gradual assignability的核心规范允许定义Any的子类这也是整个文档中信息量最大的部分。ty 的行为可以概括为一条单向规则直接或间接继承自Any的类其实例保留自身的名义类型nominal type与已声明的成员但可以渐进地赋值给任意类型反过来任意值不能赋给这种子类。直接与间接子类from typing import Any from ty_extensions._internal import reveal_mro class SubclassOfAny(Any): ... class IndirectSubclass(SubclassOfAny): ... reveal_mro(SubclassOfAny) # revealed: (class SubclassOfAny, Any, class object) reveal_mro(IndirectSubclass) # revealed: (class IndirectSubclass, class SubclassOfAny, Any, class object) reveal_type(SubclassOfAny()) # revealed: SubclassOfAny reveal_type(IndirectSubclass()) # revealed: IndirectSubclass not_a_direct_instance: SubclassOfAny 1 # error: [invalid-assignment] not_an_indirect_instance: IndirectSubclass 1 # error: [invalid-assignment] direct_as_int: int SubclassOfAny() indirect_as_int: int IndirectSubclass()reveal_mro显示Any会作为一个动态元素出现在 MRO 中而reveal_type则证明实例的推断类型仍是具体的子类并非被抹成Any。赋值的单向性非常明确int赋给SubclassOfAny报[invalid-assignment]而SubclassOfAny()赋给int合法。从源码看这个行为由 class_base.rs 中的ClassBase::Any变体支撑try_from_explicit_base第 120-132 行专门处理基类表达式是SpecialFormType::Any的情况将其保留为Self::Any而非走通用的try_from_type转换。final 类同样适用即使是final标记的类Any子类的实例依然可以赋给它们from typing import final final class FinalClass: ... f: FinalClass SubclassOfAny() final class OtherFinalClass: ... f: FinalClass | OtherFinalClass SubclassOfAny()注意最后一行Any子类实例还能赋给联合类型union哪怕联合的每个成员都是 final 类。这说明渐进赋值的粒度比单个类更细——只要赋值目标不是封闭得过于严格见下文字面量类型都可通过。动态创建的子类type(B, (A,), {})动态创建的类以及以它为基类的普通类行为保持一致from typing import Any, final from ty_extensions._internal import reveal_mro class A(Any): ... B type(B, (A,), {}) class C(B): ... final class FinalClass: ... reveal_mro(B) # revealed: (class B, class A, Any, class object) reveal_mro(C) # revealed: (class C, class B, class A, Any, class object) b: FinalClass B() c: FinalClass C()Annotated元数据不影响行为基类上的Annotated元数据如class B(Annotated[A, metadata])只是静态元数据运行时 MRO 中并不存在因此渐进赋值行为完全不变from typing import Annotated, Any, Literal, final from ty_extensions._internal import reveal_mro class A(Any): ... class B(Annotated[A, metadata]): ... final class FinalClass: ... reveal_mro(B) # revealed: (class B, class A, Any, class object) x: FinalClass B() y: Literal[1] B()泛型别名基类当Any子类再继承泛型别名时GenericSubclass[int]渐进赋值依然被保留from typing import Any, Generic, Literal, TypeVar, final T TypeVar(T) class GenericSubclass(Any, Generic[T]): ... class SubclassOfGenericAlias(GenericSubclass[int]): ... final class FinalClass: ... final: FinalClass SubclassOfGenericAlias() literal: Literal[1] SubclassOfGenericAlias()边界情况一基类表达式的类型是Any而非显式继承Any文档强调了一个容易混淆的细节基类表达式的推断类型是Any不等于显式继承Any。例如在函数中class DynamicBase(base)base: Any——此时动态 MRO 元素让实例可以赋给非 final 类但不能赋给 final 类或字面量类型这与显式Any基类形成对比from typing import Any, Literal, final class Arbitrary: ... final class FinalClass: ... def check_dynamic_base(base: Any): class DynamicBase(base): ... class IndirectSubclass(DynamicBase): ... reveal_type(DynamicBase()) # revealed: DynamicBase ordinary: Arbitrary IndirectSubclass() final: FinalClass DynamicBase() # error: [invalid-assignment] literal: Literal[1] DynamicBase() # error: [invalid-assignment] indirect_final: FinalClass IndirectSubclass() # error: [invalid-assignment] indirect_literal: Literal[1] IndirectSubclass() # error: [invalid-assignment]在 class_base.rs 中这一分支对应ClassBase::Dynamic(DynamicType::Any)第 299 行与ClassBase::Any是两个独立变体——这正是该边界能被精确区分的原因。边界情况二基类表达式的类型是Unknown同理继承自推断类型为Unknown的名字例如从无法解析的模块导入的类实例可以赋给非 final 类但不能赋给 final 类from typing import final from somewhere import UnknownBase # error: [unresolved-import] class Arbitrary: ... final class FinalClass: ... class FromUnknownBase(UnknownBase): ... reveal_type(FromUnknownBase()) # revealed: FromUnknownBase ordinary_unknown: Arbitrary FromUnknownBase() final_unknown: FinalClass FromUnknownBase() # error: [invalid-assignment]这里[unresolved-import]与[invalid-assignment]同时出现恰好演示了 ty 在无法解析场景下的降级策略ClassBase::unknown()ClassBase::Dynamic(DynamicType::Unknown)见 class_base.rs 第 46-47 行提供有限度的兼容性但不会像显式Any基类那样赋予完全的渐进赋值能力。Callable与Protocol赋值Any子类的实例还可以赋给任意Callable和Protocol类型from typing import Callable, Any, Protocol def takes_callable1(f: Callable[..., Any]): f() takes_callable1(SubclassOfAny()) def takes_callable2(f: Callable[[int], None]): f(1) takes_callable2(SubclassOfAny()) class CallbackProtocol(Protocol): def __call__(self, x: int, /) - None: ... def takes_callback_proto(f: CallbackProtocol): f(1) takes_callback_proto(SubclassOfAny()) class OtherProtocol(Protocol): x: int property def foo(self) - bytes: ... foo.setter def foo(self, x: str) - None: ... def takes_other_protocol(f: OtherProtocol): ... takes_other_protocol(SubclassOfAny())失败的结构检查错误上下文会被丢弃当赋值目标是一个联合类型其中某个元素因显式Any基类而赋值成功时ty 会丢弃来自失败结构检查的错误上下文而不是报出误导性的错误from typing import Any, Callable class CallableSubclassOfAny(Any): def __call__(self, x: int) - str: raise NotImplementedError class IncompatibleCallable: def __call__(self, x: int) - bytes: raise NotImplementedError def check_callable_union(value1: CallableSubclassOfAny | IncompatibleCallable): target1: Callable[[int], int] value1 # snapshot此时 snapshot 断言输出的诊断只指向真正不兼容的联合元素IncompatibleCallable返回类型bytes不能赋给int而不会把CallableSubclassOfAny一并标记为失败error[invalid-assignment]: Object of type CallableSubclassOfAny | IncompatibleCallable is not assignable to (int, /) - int -- src/mdtest_snippet.py:141:37 | 141 | target1: Callable[[int], int] value1 # snapshot | -------------------- ^^^^^^ Incompatible value of type CallableSubclassOfAny | IncompatibleCallable | | | Declared type info: element IncompatibleCallable of union CallableSubclassOfAny | IncompatibleCallable is not assignable to (int, /) - int info: └── type IncompatibleCallable has inferred callable type (x: int) - bytes info: └── incompatible return types: bytes is not assignable to int字面量类型Literal赋值Any子类实例还可以通过其实例类型的动态元素赋给字面量类型from typing import Any, Literal class MockAny(Any): pass x: Literal[1] MockAny()真实使用场景mock 库文档明确指出Any子类最常见的真实场景是测试 mock 库——mock 对象应当可以赋给几乎任何类型。标准库unittest.mock.MagicMock就利用了这一点from unittest.mock import MagicMock x: int MagicMock()这意味着 ty 对MagicMock的建模作为Any的隐式子类或等效处理允许它赋给任意声明类型这正是 mock 模式在类型检查下依然可用的关键。运行时属性Runtime propertiesty 对Any运行时行为的建模在 Python 3.11 上typing.Any在运行时是一个类typing_extensions.Any则始终是类在更早的 Python 版本上typing.Any是typing._SpecialForm的实例但 ty目前不建模这种差异——在所有 Python 版本上ty 都推断Any拥有类应有的全部属性from typing import Any from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_assignable_to reveal_type(Any.__base__) # revealed: type | None reveal_type(Any.__bases__) # revealed: tuple[type, ...] static_assert(is_assignable_to(TypeOf[Any], type))这与 special_form.rs 第 180 行的映射一致Self::Any被归为KnownClass::Type即把Any视为type类的实例来处理属性访问。非法用法InvalidAny有四类在运行时必然抛错的用法ty 都会给出对应的诊断。不能被参数化from typing import Any # error: [invalid-type-form] Special form typing.Any expected no type parameter def f(x: Any[int]): reveal_type(x) # revealed: UnknownAny[int]会触发[invalid-type-form]诊断定义于 diagnostic.rs 的invalid-type-form第 729 行附近且此时x的类型被降级为Unknown。不能被调用Any() # error: [call-non-callable] Object of type special-form typing.Any is not callable不能用作元类元类在底层会隐式调用Any因此同样报错class F(metaclassAny): ... # error: [invalid-metaclass] Metaclass type special-form typing.Any is not callable不能用于isinstance()但可以用于issubclass()isinstance(x, Any)在运行时抛TypeErrorty 报[invalid-argument-type]即使Any嵌套在元组里包括非字面量元组也一样# error: [invalid-argument-type] typing.Any cannot be used with isinstance(): This call will raise TypeError at runtime isinstance(, Any) isinstance(, (int, Any)) # error: [invalid-argument-type] isinstance(, (int, (str, Any))) # error: [invalid-argument-type] classes (int, Any) isinstance(, classes) # error: [invalid-argument-type]而issubclass()检查是允许的issubclass走类元层面的检查不会触发同样的运行时错误issubclass(object, Any) # no error! issubclass(object, (int, Any)) # no error! issubclass(object, (int, (str, Any))) # no error!小结一张表理解 ty 的Any行为场景ty 的行为诊断/输出x: Any 1; x foo类型恒为Anyrevealed: Anyfrom typing import Any as RenamedAny别名仍识别为Anyrevealed: Any本地定义class Any遮蔽typing.Any赋值str报[invalid-assignment]class Sub(Any)实例保留名义类型可单向渐进赋值给任意类型MRO 中含Any元素int不能赋给Sub动态基类base: Any实例可赋给非 final 类不可赋给 final/Literal赋值 final 报[invalid-assignment]Unknown基类同上但可赋性进一步受限[unresolved-import][invalid-assignment]Any[int]拒绝参数化[invalid-type-form]Any()拒绝调用[call-non-callable]metaclassAny拒绝作元类[invalid-metaclass]isinstance(x, Any)拒绝含元组嵌套[invalid-argument-type]issubclass(x, Any)允许无错误如果你想在自己的项目里验证这些行为可以直接阅读或运行 annotations/any.md 中的代码片段——每个代码块都是可执行的可断言测试reveal_type、reveal_mro与# error:注释共同构成了 ty 对Any语义的可回归验证体系。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考