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

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

2026-09-08 22:19:29作者:鲍丁臣Ursa

@final 是 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.md):

  • What it does:检查 @final 装饰器是否被应用到了非方法(non-method)函数上;
  • Why is this bad@final 装饰器只在**方法(method)类(class)**上才有意义。把它应用到模块级函数或嵌套函数上不会有任何效果,且很可能是写作者的失误。

从声明源码看,规则注册在 crates/ty_python_semantic/src/types/diagnostic.rs

declare_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_METHOD
summary 检测被应用到非方法函数上的 @final
默认级别 Error(默认开启,报错级)
状态 stable("0.0.20")(自该版本起稳定)

二、典型误用示例与诊断消息

规则文档给出的最小示例直接命中"模块级函数"这一场景(final-on-non-method.md):

from 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.md):

from 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,禁止子类覆写/继承"。类型系统里它的可检验语义只有两类:

  1. 禁止对 final 方法做覆写(override)——由 override-of-final-method 规则负责;
  2. 禁止继承 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.md):stub 文件里 @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");
}

实现要点可以拆解为三层:

  1. 识别 @final 装饰器。在遍历装饰器列表(function.rs)时,若某装饰器推导出的类型是 Type::FunctionLiteral 且其 known 归属为 KnownFunction::Final,就把它记为 final_decorator。由于 final 的"知名函数"识别同时覆盖 typing.finaltyping_extensions.final,两种导入来源都会命中,这一点在 mdtest/final.md 中被同时验证(typingtyping_extensions 各有用例)。

  2. 判断函数所处的作用域类别self.scope().file_scope_id(db) 拿到的是定义这条函数语句所在作用域:模块级函数处于 module scope,嵌套函数处于外层函数(method 或 function)作用域,只有直接写在类体里的成员函数其所在作用域才是 class scope。代码据此用 .kind().is_class() 判负——不是类作用域,就说明这不是一个"方法"。

  3. 生成诊断。命中后通过 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 提供了 checkexplain 子命令(入口见 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-valueoverride-of-final-variable 等规则的语义域),而不是 @final

给代码审查与教学场景的三点经验:

  1. 方法 ≠ 嵌套在函数里的函数。判断依据是"定义语句所在作用域是否为类作用域"(源码判据见 function.rs),而非函数长什么样。
  2. @final 的最佳位置紧贴被约束的方法/类定义。绕经 lossy_decorator、多重恒等装饰器之后再识别 final 是类型检查器的实现负担(见 mdtest/final.md 的边界用例),从源头上把 @final 写在声明处最省心。
  3. 不要把 @final@abstractmethod 混用:两者语义互斥(abstract-and-final-method),抽象的必须被覆写,final 的禁止被覆写,鱼与熊掌不可兼得(mdtest/final.md)。

总结

final-on-non-method 是 ruff 仓库中 ty 类型检查器针对 @final 误用场景的第一道防线。它以 Error 级别拦截一切施加在模块级函数与嵌套函数上的无效 @final,通过与 override-of-final-methodsubclass-of-final-class 等规则的配合,共同保障 final 语义只在方法/类这两个真正有继承语义的对象上被表达。想要深入研究的读者,可以从三条线索继续探索本仓库:规则文档 lint_docs/final-on-non-method.md、注册声明 types/diagnostic.rs、实现与测试 types/infer/builder/function.rsmdtest/final.md

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391