ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ruff ty 类型检查器规则解析:`@final` 不得用于非方法函数(final-on-non-method)

2026/9/10 9:56:07 拓冰建站 浏览量
ruff ty 类型检查器规则解析:`@final` 不得用于非方法函数(final-on-non-method) ruff ty 类型检查器规则解析final不得用于非方法函数final-on-non-method【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/rufffinal是 Python 类型系统里用来表达禁止继承/禁止覆写语义的关键装饰器但它并非对任何函数都有效把它贴到模块级函数或嵌套函数上不会产生任何运行时与类型层面的约束只会让代码意图落空。本文以 ruff 仓库中ty基于 Rust 的高性能类型检查器内置的final-on-non-method规则为对象讲解该规则的检测目标、触发条件、消息格式、源码实现与正确写法帮助你写出语义自洽的类型标注代码也能让读者理解在 ruff 项目中final相关规则的实现与测试思路。本规则在类型检查器ty中默认启用级别为 Error定位在 final-on-non-method.md 这一规则文档中可直接作为开发与教学的一手资料使用。一、规则是什么检测施加在非方法函数上的final规则文档开宗明义crates/ty_python_semantic/resources/lint_docs/final-on-non-method.mdWhat it does检查final装饰器是否被应用到了非方法non-method函数上Why is this badfinal装饰器只在**方法method和类class**上才有意义。把它应用到模块级函数或嵌套函数上不会有任何效果且很可能是写作者的失误。从声明源码看规则注册在 crates/ty_python_semantic/src/types/diagnostic.rsdeclare_lint! { #[doc include_str!(../../resources/lint_docs/final-on-non-method.md)] pub(crate) static FINAL_ON_NON_METHOD { summary: detects final applied to non-method functions, status: LintStatus::stable(0.0.20), default_level: Level::Error, } }值得注意的工程细节是该 Markdown 文档不是游离于代码之外的说明书而是通过include_str!直接内嵌为规则的 doc 注释再在 diagnostic.rs 中通过registry.register_lint(FINAL_ON_NON_METHOD)完成注册。也就是说lint_docs 目录下的每份规则文档与代码中的 Lint 一一对应既是文档又是 Rust 编译单元的一部分。规则元数据汇总如下项目值规则代码诊断输出中的标识final-on-non-method静态变量名FINAL_ON_NON_METHODsummary检测被应用到非方法函数上的final默认级别Error默认开启报错级状态stable(0.0.20)自该版本起稳定二、典型误用示例与诊断消息规则文档给出的最小示例直接命中模块级函数这一场景final-on-non-method.mdfrom typing import final # final is not allowed on non-method functions final # error def my_function() - int: return 0在ty的类型检查测试语料中final施加到非方法函数的错误被更细致地展开为三类场景crates/ty_python_semantic/resources/mdtest/final.mdfrom typing import final final # error: [final-on-non-method] final cannot be applied to non-method function func1 def func1(): ... # Nested function decorated with final is also invalid def outer(): final # error: [final-on-non-method] def inner(): ... # A function nested inside a method is also not a method class F: def method(self): final # error: [final-on-non-method] def not_a_method(): ...可以看到ty实际输出的错误代码为[final-on-non-method]完整消息为final cannot be applied to non-method function func1。这里有个非常容易误解的边界模块顶层函数func1→ 报错普通函数体内定义的嵌套函数inner→ 报错方法体内定义的嵌套函数not_a_method→ 同样报错因为它的直接宿主作用域是方法这个函数作用域而不是类作用域。所谓 method指的是直接声明在类体中的函数成员方法体内的局部函数并不算方法。三、为什么final只对方法和类有意义typing.final/typing_extensions.final的语义是标记一个方法或类为 final禁止子类覆写/继承。类型系统里它的可检验语义只有两类禁止对 final 方法做覆写override——由override-of-final-method规则负责禁止继承 final 类——由subclass-of-final-class规则负责。一个模块级函数或嵌套函数既没有子类覆写这一概念也没有继承者概念final不会影响其可调用性、参数检查或返回值推断。因此把它写在非方法函数上等于向读者宣称一条不存在且永远不会被验证的约束——检查器将其视为错误是合理的保守选择。在 crates/ty_python_semantic/src/types/diagnostic.rs 中可以看到与final语义相关的完整规则族规则代码summary关注点override-of-final-method检测对 final 方法的覆写子类不可覆写 final 方法override-of-final-variable检测对Final类变量的覆写子类不可覆写 final 类变量subclass-of-final-class检测 final 类的子类final 类不可被继承ineffective-final检测类型检查器无法解释的final()调用无效的final表达final-without-value检测没有赋值的Final声明Final声明形式错误abstract-and-final-method检测既 abstract 又 final 的方法两种语义互斥final-on-non-method正是这个规则族中负责收窄final合法使用范围的一环它不负责验证 final 语义是否被破坏而是从源头拦截把final用错了对象的低级错误。四、合法用法方法和类与上述误用相对final在以下位置是合法的from typing import final # 1) 普通方法实例方法 class Service: final def run(self) - None: ... # 2) 类方法 / 静态方法 / 属性——装饰器顺序无关紧要 final classmethod def create(cls) - Service: ... final property def version(self) - str: ... # 3) 构造方法同样适用禁止子类改写 __init__ final def __init__(self) - None: ... # 4) 类 final class ImmutableConfig: pass以上合法性均可以从ty自身的测试断言得到印证。例如 mdtest/final.md 中的父类同时用final标注了实例方法、property多种装饰器顺序、classmethod、staticmethod且这些声明均不产生final-on-non-method错误而子类对它们的覆写则统一触发[override-of-final-method]。这组对照测试清楚地说明了两类规则的职责分工前者把守装饰器放置位置后者把守覆写行为。此外测试还覆盖了final与overload的组合规则mdtest/final.mdstub 文件里final应放在第一个 overload 上运行期文件里final应只放在实现函数上——放错位置会触发[invalid-overload]而非本规则。五、源码级实现ty如何在推断函数时触发该诊断final-on-non-method的触发点位于函数类型推断构造器 crates/ty_python_semantic/src/types/infer/builder/function.rs// Check for final applied to non-method functions. // final is only meaningful on methods and classes. if let Some(final_decorator) final_decorator !self .index .scope(self.scope().file_scope_id(db)) .kind() .is_class() let Some(builder) self .context .report_lint(FINAL_ON_NON_METHOD, final_decorator) { let mut diagnostic builder.into_diagnostic(format_args!( final cannot be applied to non-method function {name}, )); diagnostic.info(final is only meaningful on methods and classes); }实现要点可以拆解为三层识别final装饰器。在遍历装饰器列表function.rs时若某装饰器推导出的类型是Type::FunctionLiteral且其known归属为KnownFunction::Final就把它记为final_decorator。由于final的知名函数识别同时覆盖typing.final与typing_extensions.final两种导入来源都会命中这一点在 mdtest/final.md 中被同时验证typing与typing_extensions各有用例。判断函数所处的作用域类别。self.scope().file_scope_id(db)拿到的是定义这条函数语句所在作用域模块级函数处于 module scope嵌套函数处于外层函数method 或 function作用域只有直接写在类体里的成员函数其所在作用域才是 class scope。代码据此用.kind().is_class()判负——不是类作用域就说明这不是一个方法。生成诊断。命中后通过report_lint(FINAL_ON_NON_METHOD, final_decorator)上报并把诊断锚点span落在final装饰器本身而不是函数名上便于用户在长函数前快速定位出错的那一行装饰器消息体带上函数名附注info则说明正确语义。从代码结构可以推断final放在普通非类作用域内会被直接拦截并continue跳过后续装饰器处理因此该函数不会携带 final 标记进入任何覆写/继承判定——这与规则文档施加无效果的表述一致错误在声明处即被捕获不会留下隐患蔓延到下游检查。六、如何触发与验证mdtest 测试与 CLI本仓库把文档、源码、测试三者的关系组织得很清晰lint_docs/下的 Markdown规则的人类可读文档即本文主体被include_str!嵌入代码mdtest/下的 Markdown规则的可执行测试语料。其格式约定见 crates/ty_test/README.md通过 resources/README.md 可知它们由tests/mdtest.rs集成测试执行mdtest/测试通过行内注释断言结果语法形如# error: [rule-code] message解析逻辑见 crates/mdtest/src/assertion.rs规则文档中的代码块同样遵守这套断言注释。如果你想在自己的项目中复现本规则ty检查器就构建在本仓库的 crates/ty 中其 CLI 提供了check与explain子命令入口见 crates/ty/src/lib.rs主程序见 crates/ty/src/main.rs。大致用法# 对当前目录执行类型检查命中 final-on-non-method 时会以 Error 级别报出 ty check . # 查看某条规则的说明文档 ty explain final-on-non-method验证脚本可以这样组织# misuse.py from typing import final final # error: [final-on-non-method] def helper() - int: return 42对misuse.py运行ty check你会得到与 mdtest/final.md 中完全一致的诊断规则代码、消息文本、装饰器行号位置均已由 mdtest 快照锁定。由于默认级别是Error此类代码会被当作类型检查错误直接拦截而不会静默通过。七、修复方式与编写建议该规则不提供自动修复autofix——ty的选择是与其替你删除一行可能有争议的装饰器不如把语义决策留给开发者。正确做法很简单删除模块级/嵌套函数上的final若函数的真实意图就是不希望被子类覆写那么它本应作为方法直接声明在类体中此时final合法且生效若想约束的是变量不可被覆写/重新绑定应改用类型限定符Final对应final-without-value、override-of-final-variable等规则的语义域而不是final。给代码审查与教学场景的三点经验方法 ≠ 嵌套在函数里的函数。判断依据是定义语句所在作用域是否为类作用域源码判据见 function.rs而非函数长什么样。final的最佳位置紧贴被约束的方法/类定义。绕经lossy_decorator、多重恒等装饰器之后再识别 final 是类型检查器的实现负担见 mdtest/final.md 的边界用例从源头上把final写在声明处最省心。不要把final与abstractmethod混用两者语义互斥abstract-and-final-method抽象的必须被覆写final 的禁止被覆写鱼与熊掌不可兼得mdtest/final.md。总结final-on-non-method是 ruff 仓库中ty类型检查器针对final误用场景的第一道防线。它以Error级别拦截一切施加在模块级函数与嵌套函数上的无效final通过与override-of-final-method、subclass-of-final-class等规则的配合共同保障 final 语义只在方法/类这两个真正有继承语义的对象上被表达。想要深入研究的读者可以从三条线索继续探索本仓库规则文档 lint_docs/final-on-non-method.md、注册声明 types/diagnostic.rs、实现与测试 types/infer/builder/function.rs 与 mdtest/final.md。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考