资讯动态

Ruff 类型检查器 ty 对 `@no_type_check` 装饰器的完整支持解析

发布时间:2026/9/12 3:43:39 来源:尧图企业网站定制
Ruff 类型检查器 ty 对no_type_check装饰器的完整支持解析【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffno_type_check是 Pythontyping模块提供的一个运行时指令用于告诉类型检查器跳过某个函数内部的全部类型检查。在 Ruff 内置的静态类型检查器 ty 中该装饰器通过一组 mdtest 用例no_type_check.md被精确定义与验证。本文将基于该文档并结合 ty_python_semantic 的源码实现逐条剖析no_type_check在函数体、嵌套作用域、装饰器表达式、默认值与返回注解等场景下的抑制语义帮助开发者准确理解并合理使用这一静态检查逃生舱。语义基线no_type_check到底抑制什么根据 typing 规范 的定义支持no_type_check装饰器的类型检查器应当抑制def语句及其函数体包括嵌套函数、嵌套类内的全部类型错误忽略所有参数注解与返回注解将函数视同未注解来处理。ty 完全遵循这一基线并将它落实为两个机制推理标志inference flag与已知装饰器known decorator识别。从源码结构看二者共同构成了no_type_check的完整实现路径。在 types/function.rs 中no_type_check被建模为FunctionDecorators位标志集合的一个成员const NO_TYPE_CHECK 1 1;同时types/function.rs 将运行时函数typing.no_type_check对应KnownFunction::NoTypeCheck映射到该标志。这意味着当类型检查器在装饰器列表中遇到no_type_check时它能够静态地将其识别为已知装饰器而不是普通的未知函数调用。核心机制推理标志IN_NO_TYPE_CHECKty 的抑制能力本质上依赖一个贯穿整个推断过程的状态位。在 types/context.rs 中is_in_no_type_check()检查当前是否处于抑制状态fn is_in_no_type_check(self) - bool { if self.inference_flags.contains(InferenceFlags::IN_NO_TYPE_CHECK) { return true; } // ... index .ancestor_scopes(scope_id) .filter_map(|(_, scope)| scope.node().as_function()) .filter_map(|node| { infer_definition_types(self.db(), index.expect_single_definition(node)) .undecorated_type() .and_then(Type::as_function_literal) }) .any(|function_ty| { function_ty.has_known_decorator(self.db(), FunctionDecorators::NO_TYPE_CHECK) }) }这段实现透露了两个关键设计决策当前作用域优先若当前推理上下文的IN_NO_TYPE_CHECK标志已被置位则直接判定处于抑制区。祖先作用域回退否则沿作用域链自底向上遍历所有祖先函数检查其未装饰类型undecorated_type是否带有no_type_check装饰器。源码注释特别强调使用未装饰类型而非绑定类型原因在于其他装饰器如未知装饰器可能把函数类型改写为非FunctionLiteral从而掩盖no_type_check的身份。正是这个回退逻辑支撑了文档中嵌套函数与嵌套类内的错误同样被抑制的语义——嵌套作用域通过祖先函数链感知到抑制状态。至于第 2 点为什么用undecorated_type()可从 types/infer/builder.rs 的装饰器区域推断逻辑得到印证下文详述。函数体与嵌套作用域内的错误抑制文档先用三个最小用例确立了抑制的覆盖范围以下代码均不会产生任何诊断函数体中的错误from typing import no_type_check no_type_check def test() - int: return a 5尽管a未定义、返回值也与- int注解冲突但由于no_type_check的存在二者都被静默。嵌套函数中的错误from typing import no_type_check no_type_check def test() - int: def nested(): return a 5嵌套类中的错误from typing import no_type_check no_type_check def test() - int: class Nested: def inner(self): return a 5第三个用例尤其值得注意Nested.inner中的a即使放在普通函数中必然报unresolved-reference未解析引用也会因外层函数被no_type_check装饰而整体豁免。这正是上文祖先函数链回退机制的实际效果——is_in_no_type_check()通过ancestor_scopes找到外层test并确认其带有NO_TYPE_CHECK装饰器。从实现上看函数体的抑制是通过在装饰器推断阶段设置标志完成的。在 builder/function.rs 中Some(KnownFunction::NoTypeCheck) { // If the function is decorated with the no_type_check decorator, // we need to suppress any errors that come after the decorators. self.context.inference_flags | InferenceFlags::IN_NO_TYPE_CHECK; continue; }一旦标志被置位后续针对函数体、参数、返回值的诊断都会在生成前被拦截。装饰器应用错误当前统一抑制的取舍文档专门用一节讨论了**装饰器应用错误decorator-application errors**的处理即装饰器本身调用时产生的类型错误如参数类型不匹配。当前行为是只要是no_type_check装饰的函数其全部装饰器应用错误都会被抑制无论该错误来自no_type_check之前还是之后的装饰器。from typing import no_type_check def takes_int(value: int) - int: return value # TODO this should be an error: takes_int no_type_check def before() - None: ... # no error, swallowed by no_type_check: no_type_check takes_int def after() - None: ... # error: [invalid-argument-type] takes_int def checked() - None: ...对照用例可见before中takes_int位于no_type_check之前按直觉应当报invalid-argument-type但当前实现同样将其吞掉——文档明确标注了TODO this should be an errorafter中takes_int位于no_type_check之后错误被吞掉属于预期行为未加装饰的checked正常报错作为对照组证明错误本身真实存在。文档也指出了 TODO 方向更符合直觉、且与下方装饰器表达式错误处理一致的做法是仅抑制源码顺序上位于no_type_check之后的装饰器所产生的错误。这一行为差异在源码中也有对应痕迹从 builder/function.rs 的装饰器遍历逻辑看KnownFunction::NoTypeCheck分支通过continue跳过自身而装饰器应用错误的抑制范围则由标志位的置位时机决定尚未按源码顺序精确切分。装饰器表达式错误与 Pyright / mypy 的有意分歧ty 在**装饰器表达式decorator expression**的诊断抑制上做了与 Pyright、mypy 不同的选择。文档明确说明Unlike Pyright and mypy, we also suppress diagnostics in decorator expressions appearing after theno_type_checkdecorator.即 ty会抑制出现在no_type_check之后的装饰器表达式中的诊断如未解析引用理由是这更贴近 Python 装饰器的运行时语义——装饰器按源码顺序从下往上求值先求值no_type_check之上的装饰器表达式时no_type_check的抑制尚未生效。from typing import no_type_check no_type_check unknown_decorator # 不报错被 no_type_check 抑制 def test() - int: return a 5而位于no_type_check之前的装饰器表达式则不被抑制from typing import no_type_check unknown_decorator # error: [unresolved-reference] no_type_check def test() - int: return a 5实现这一精确时序的关键在于独立的装饰器推断区域 infer_region_function_decoratorsfor decorator in function.node(self.module()).decorator_list { let decorator_type self.infer_decorator(decorator); if let Type::FunctionLiteral(function) decorator_type let Some(KnownFunction::NoTypeCheck) function.known(self.db()) { // Match infer_function_definition: suppress diagnostics that follow // no_type_check, including later decorators. self.context.inference_flags | InferenceFlags::IN_NO_TYPE_CHECK; } }该区域按源码顺序逐个推断装饰器表达式一旦遇到no_type_check就立即置位标志因此位于其后的装饰器表达式在求值时标志已置位 → 诊断被抑制位于其前的装饰器表达式在求值时标志尚未置位 → 诊断正常上报。源码注释 suppress diagnostics that followno_type_check, including later decorators 与该节文档完全对应同时印证了 context.rs 中is_in_no_type_check设计的前后一致性。默认值与返回注解的抑制no_type_check的抑制范围不止函数体还包括参数默认值与返回注解。默认值中的错误from typing import no_type_check no_type_check def test(a: int test): return x 5a: int test的默认值类型不匹配被静默。返回位置返回注解中的错误from typing import no_type_check no_type_check def test() - Undefined: return x 5Undefined未定义导致的unresolved-reference同样被抑制。这两类场景在源码中有专门的处理入口。在 builder/function.rs 中infer_function_annotationsL718-L732在推断延迟注解前调用suppress_errors_for_no_type_checkinfer_function_defaultsL734-L764在推断默认值前调用同一辅助函数。辅助函数 suppress_errors_for_no_type_check 的实现很简洁只要函数装饰器列表非空且已知装饰器标志包含NO_TYPE_CHECK就置位IN_NO_TYPE_CHECK标志if !function.decorator_list.is_empty() function_known_decorator_flags(self.db(), definition) .contains(FunctionDecorators::NO_TYPE_CHECK) { // Decorator expressions and their diagnostics belong to their own inference query. // Signature and default inference only need to know whether errors are suppressed. self.context.inference_flags | InferenceFlags::IN_NO_TYPE_CHECK; }注释还点明了一个架构细节装饰器表达式及其诊断归属独立的推理查询inference query签名与默认值推断只需获知错误是否被抑制因此这里直接读取标志即可无需重复推断装饰器。函数声明上的后置检查抑制no_type_check还会抑制函数声明上的后置检查post-inference checks。文档用例from typing import no_type_check no_type_check def positional(x: int, __y: str): ...__y以双下划线开头会被视为仅位置参数positional-only正常情况下在声明上会触发相应诊断但此处同样被no_type_check吞掉。从源码结构看这类函数声明层面的后置检查统一受is_in_no_type_check()门控types/context.rs 附近可见其调用点从而保证函数声明 函数体 嵌套作用域的抑制范围始终一致。边界类上的no_type_check不被支持文档明确ty 不支持把no_type_check用在类上。规范本身对类的行为当前未定义Pyright 与 mypy 同样不支持因此 ty 也不做特殊处理from typing import no_type_check no_type_check class Test: def test(self): return a 5 # error: [unresolved-reference]注意这里a 5的unresolved-reference照常报错与函数场景形成鲜明对比。文档同时给出了未来改进方向可能在检测到类上的no_type_check注解时发出诊断但目前尚未实现。这与 context.rs 的回退逻辑相互印证——该回退只遍历as_function()的祖先作用域类作用域并不在检查范围内。抑制区内的ty: ignore注解产生unused-ignore-commentno_type_check与行内忽略指令的交互同样被精确测试。当函数整体已被抑制时块内再写ty: ignore就属于冗余from typing import no_type_check no_type_check def test(): # error: [unused-ignore-comment] Unused ty: ignore directive return x 5 # ty: ignore[unresolved-reference]由于a 5的unresolved-reference本就被no_type_check吞掉这里ty: ignore[unresolved-reference]没有任何可忽略的目标ty 因而上报unused-ignore-comment未使用的忽略注释诊断。这表明 ty 的抑制机制是分层的no_type_check让错误不存在而ty: ignore本身仍在被跟踪两者之间的冲突会被精确识别。类似的行内忽略语义可对照同目录下的 type_ignore.md、ty_ignore.md 与 blanket_ignore.md 进一步了解。mdtest文档即测试的验证方式本文档位于crates/ty_python_semantic/resources/mdtest/suppressions/属于 ty 的mdtest 测试体系Markdown 文件本身就是测试用例其中的代码块会被真实送入类型检查器运行标注的error: [code]断言期望诊断无标注的代码块断言无诊断。同一测试基础设施在 Cargo.toml 中声明name mdtest并与同目录的 deprecated.md 中no_type_check函数内的调用同样适用抑制的用例互相呼应。因此本文所引用的每一条行为都可以直接通过对该目录运行 mdtest 来验证不存在脱离测试的推测性描述。小结与使用建议把文档语义与源码实现对照后no_type_check在 ty 中的完整行为可以归纳为一张行为矩阵位置是否抑制函数体含嵌套函数、嵌套类✅ 抑制参数注解、返回注解✅ 抑制视为未注解默认值中的类型错误✅ 抑制函数声明上的后置检查✅ 抑制no_type_check之后的装饰器表达式错误✅ 抑制与 Pyright/mypy 不同no_type_check之前的装饰器表达式错误❌ 不抑制全部装饰器应用错误无论前后✅ 当前统一抑制含 TODO 待优化项类上的no_type_check❌ 不支持类体内照常检查抑制区内的ty: ignore⚠ 报unused-ignore-comment实际使用中需要注意no_type_check是整函数级的开关粒度远大于行内ty: ignore适合用于这段代码类型很复杂、不值得检查的场景但会同时牺牲参数/返回值的类型信息装饰器表达式与装饰器应用错误的抑制范围存在细微差别前者按源码顺序切分后者暂未切分升级 ty 版本时若依赖了装饰器错误报出需留意该 TODO 的后续变化类不支持该装饰器对类做类型豁免目前只能依赖行内忽略或重构。若需在本地复现文中所有断言可对 resources/mdtest/suppressions 目录运行 ty 的 mdtest 测试阅读源码时可重点关注 infer/builder/function.rs、infer/builder.rs 与 types/context.rs 三个文件中的IN_NO_TYPE_CHECK相关代码路径。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价