尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Ruff 仓库内 ty 类型检查器的 `override-of-final-method` 规则解析:拦截子类对 `@final` 方法的非法重写

Ruff 仓库内 ty 类型检查器的 `override-of-final-method` 规则解析:拦截子类对 `@final` 方法的非法重写 Ruff 仓库内 ty 类型检查器的override-of-final-method规则解析拦截子类对final方法的非法重写【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffoverride-of-final-method是本仓库内置类型检查器 ty源码位于 crates/ty_python_semantic的一条稳定规则一旦超类方法被final修饰就禁止任何子类以重定义、赋值类属性等方式覆盖它。本文以该规则的设计文档 override-of-final-method.md 为主体结合 诊断源码、规则参考文档 与 mdtest 测试完整讲解规则语义、默认行为、触发示例、诊断输出格式、自动修复边界以及装饰器顺序、重载、继承链等边界场景帮助你彻底理解并正确规避这类类型错误。规则是什么检测对final方法的重写What it doesChecks for methods on subclasses that override superclass methods decorated withfinal.规则原文给出的一句话定义是检查子类中是否存在对被final装饰的超类方法进行重写的成员定义。这条规则不关心运行期行为它属于纯静态的类型检查范畴——无论被检查文件是普通.py、桩文件.pyi还是 notebook只要声明层面构成对 final 方法的重写就会触发。在仓库实现中该规则的声明位于 crates/ty_python_semantic/src/types/diagnostic.rs#L984-L991declare_lint! { #[doc include_str!(../../resources/lint_docs/override-of-final-method.md)] pub(crate) static OVERRIDE_OF_FINAL_METHOD { summary: detects overrides of final methods, status: LintStatus::stable(0.0.1-alpha.29), default_level: Level::Error, } }从中可以看到三件关键事实规则代码标识OVERRIDE_OF_FINAL_METHOD命令行/诊断中呈现为override-of-final-method默认等级为error这意味着在默认配置下运行检查就会直接报错而不是警告或忽略自 0.0.1-alpha.29 起即标记为稳定不属于实验性规则。官方生成的规则参考页位于 crates/ty/docs/rules.md#L4322-L4358也确认了Default level: error与版本号。值得注意的机制细节规则文档lint_docs/*.md通过include_str!宏直接嵌入到 Rust 源码的 doc 注释中因此源码cargo doc生成的 API 文档与规则参考页内容天然一致修改文档会同步进入两个出口。为什么这是错误的final的语义契约Why is this badDecorating a method withfinaldeclares to the type checker that it should not be overridden on any subclass.给方法打上final装饰器等同于向类型检查器作出一个静态契约该方法不允许在任何子类中被重写。原因在于 API 设计中作者一旦用final标记方法通常意味着该方法的实现对类的内部不变量至关重要覆盖会破坏对象状态的一致性该方法的行为被下游代码信任例如文档承诺、序列化协议、生命周期钩子重写会导致契约破裂作者未来可能将该方法实现替换为 C 扩展、缓存代理或其他不可子类化的形态。注意一个容易被误解的点final与Final类型限定符一样是纯静态层面的声明typing.final在运行期几乎不做任何事它返回原函数。因此运行期你仍能覆盖它——类型检查器存在的意义正是在编译期而非运行期拦截这类设计错误。这一点与typing/typing_extensions的关系也很重要规则测试标题即写作 Tests for thetyping(_extensions).finaldecorator见 final.md说明无论from typing import final还是from typing_extensions import final只要被识别为内建的 final 装饰器规则都会生效。最小触发示例规则文档给出的最小示例完整如下保留原文档全部内容from typing import final class A: final def foo(self): ... class B(A): def foo(self): ... # errorclass B(A)中重定义了超类A中被final装饰的foo因此第 22 行会报出override-of-final-method。把第 22 行注释为# error是 mdtest 的约定写法实际诊断输出形如error[override-of-final-method]: Cannot override A.foo help: Remove the override of foo对这条规则的实际运行验证非常简单克隆本仓库后用 crates/ty 下构建出的ty类型检查器直接检查上面的文件即可复现错误仓库内的 crates/ty_python_semantic/resources/mdtest/snapshots 目录保存了对应的自动快照snapshot测试产物可用于比对真实输出。诊断输出完整消息结构与多行定位标注override-of-final-method并非简单地打一行字而是通过report_overridden_final_method函数diagnostic.rs#L5159-L5343构造一个带多级标注的诊断对象。从快照测试对应 final.md 的通过把函数赋给类变量来重写场景可以看到真实的完整输出error[override-of-final-method]: Cannot override Base.method -- src/derived.py:5:5 | 5 | method replacement_method # error: [override-of-final-method] | ^^^^^^ Overrides a definition from superclass Base info: Base.method is decorated with final, forbidding overrides -- src/base.py:4:5 | 4 | final | ------ 5 | def method(self) - None: ... | ------ Base.method defined here help: Remove the override of method对照源码可以把这段输出逐层拆解主标题Cannot override{superclass_name}.{member}L5200-L5201主标注消息primary annotationOverrides a definition from superclass{superclass_name}L5202-L5204定位在子类中被判定的重写定义上简化消息concise messageCannot override final member{member}from superclass{superclass_name}L5205-L5207用于编辑器内联展示等只读一行文本的场合与多行摘要消息不同Info 级子诊断{superclass_name}.{member}is decorated withfinal, forbidding overridesL5209-L5214并把两个 secondary 标注落在超类上——一个是超类方法定义本身...defined here另一个是final 装饰器所在 spanL5239-L5243让用户一眼看到是谁的哪个装饰器在禁止重写Help 文本Remove the override of {member}或针对重载/属性场景的变体见下文自动修复节。源码里还有两个容易忽略的工程细节属性property重写要定位到 getter。当子类成员是以property重写 final 方法时代码刻意做了一次劫持如果被检查定义是函数定义且子类类型是PropertyInstance就改取其 getter 的定义进行报告避免把错误标在 setter 上L5170-L5185超类与子类同名时的消歧。当子类在自己的模块里恰好与超类同名例如多文件场景中class Foo(module1.Foo)superclass_name会改用超类的qualified_name全限定名来避免混淆L5194-L5198这一场景在测试 final.md#L153-L174 中被专门覆盖。底层实现如何判定成员是 final以及构成重写要发出该诊断类型推导器需要同时回答两个问题。问题一超类方法是不是 final判定发生在 report_overridden_final_method 内它从超类同名方法定义中找出第一个带 final 装饰器的函数L5216-L5221let first_final_superclass_definition superclass_method_defs .iter() .find(|function| function.has_known_decorator(db, FunctionDecorators::FINAL)) .expect( At least one function definition in the superclass should be decorated with final, );底层把装饰器已知化识别源码 function.rs#L198 将KnownFunction::Final即typing.final/typing_extensions.final映射为FunctionDecorators::FINAL随后超类静态成员推断时即记录该标志参考 static_literal.rs 与 class.rs 中的KnownFunction::Final分支。对**桩文件stub**中的重载还会调用first_overload_or_implementation取得首个重载或实现以保证标注位置正确L5223-L5229。问题二子类成员是否构成对 final 方法的重写这一步在类成员合并/重写分析阶段完成——例如 overrides.rs#L733 在处理属性property重写时就会检查父类同名方法是否带FunctionDecorators::FINAL。重写既包括常规的def foo也包括把函数赋给类变量method other_fn、用property替换等方法它们最终都会汇聚到同一报告函数。自动修复Autofix与刻意不修复的边界不是所有命中都附带自动修复。源码中修复逻辑的取舍非常讲究L5247-L5342总结如下表子类重写形态提供的 help自动修复普通def方法唯一成员Remove the override of {member}有将整个函数体替换为pass防止类体变成空语法错误普通def方法非唯一成员同上有直接删除该函数定义区间带多个重载的方法Remove all overloads for {member}/Remove all overloads and the implementation...有逐个删除所有重载及实现同样遵守仅剩唯一成员则替换为pass带 setter 的属性Remove the getter and setter for {member}无类变量赋值method replacement_methodRemove the override of {member}无为何后两类刻意不给修复源码注释给出了原因属性重写若删除 getter还必须一并删除xxx.setter甚至xxx.deleter的整段定义而当前定义追踪尚未精确到足以保证安全L5247-L5250赋值式重写method some_function的函数可能定义在另一个文件里安全修复应当是删除这条赋值语句而删除跨文件赋值同样未实现L5251-L5253——测试 final.md#L329-L360 明确验证了发出诊断但不提供 autofix的行为。此外所有自动修复都被标记为unsafe edits并带IsolationLevel::Group隔离L5291-L5297确保批量修复时各修复互不冲突。覆盖场景矩阵装饰器顺序、特殊成员与继承链规则文档之外仓库在 mdtest/final.md 中用近 1500 行测试把规则边界钉得非常细以下是最值得了解的几类场景。1. 成员形态与装饰器顺序final与property、classmethod、staticmethod组合时无论先后顺序均被识别。测试final.md#L43-L100覆盖了final property、property final、classmethod final、staticmethod final等全部排列子类中用属性 getter、classmethod、staticmethod 重写都会报错属性场景下连my_property.setter/my_property.deleter补全也会被一并判定为重写。2. 构造函数也受保护__init__同样是方法。子类重定义被final修饰的__init__会触发同一规则final.md#L362-L373。3. 重载overload方法的特殊约定重载场景有明确规范final.md#L176-L298桩文件中final应加在第一个重载上stub.pyi的Good类把final放在后续重载上会被同族规则invalid-overload报错运行期文件中final只应加在实现函数上无论哪种形态只要超类任一可达重载带 final子类重写这些重载中任意一个都会被本规则捕获。4. 继承链上的只报一次如果B(A)重写了A的 final 方法且自己也加了final然后C(B)再次重写那么C处只发一条override-of-final-method不会沿链累积两条final.md#L375-L396。5. 跨模块同名类的消歧超类与子类同名、分处两个模块时class Foo(module1.Foo)诊断仍能正确指向module1.Foo.f这就是前文提到的 qualified_name 消歧逻辑的测试来源final.md#L153-L174。6. 条件定义与可达性规则只统计可达的 final 定义类体内if coinflip(): final def method1这类可能定义场景只要某个分支定义了 final 版本子类重写就会被报final.md#L528-L606基于sys.version_info的静态分支中不可达分支里的 final 定义不参与判定在 Python 3.10 环境下写在else:即 3.10 以下分支里的 final 方法可以被安全重写final.md#L608-L655重载同理L657-L705。7. 已知但不报告的边界实现备注测试中还以 TODO 形式标注了当前实现刻意从宽的场景例如final 方法被一个丢失签名lossy的装饰器包裹后再重写decorated_1/decorated_2、实例属性隐式覆盖 final 方法self.method: Any 42等处尚未发出诊断final.md#L98-L100、L514-L526。这些属于后续演进空间不是规则的既定行为不应当作约束依赖。8. 只对字面函数定义传播 final与其他检查器对齐若超类 final 方法先被赋给另一个类做类属性class B: method A.method再在C(B)中重写method当前实现不报错final.md#L300-L327。测试注释说明这是刻意选择mypy 与 pyright 同样不报为最大化兼容性而跟随——尽管这与它们对Final限定符跨作用域传播的处理在语义上并不完全一致未来可能调整。相关规则家族一条完整的 final 语义防线override-of-final-method并非孤立规则。在 diagnostic.rs#L966-L1045 附近ty 围绕final建立了完整的规则家族全部默认等级为error规则检查内容文档subclass-of-final-class继承被final装饰的类subclass-of-final-class.mdoverride-of-final-method子类重写final方法本文override-of-final-method.mdoverride-of-final-variable子类覆盖Final类变量override-of-final-variable.mdineffective-final以无法被类型检查器理解的方式调用final()ineffective-final.mdfinal-on-non-method把final用在模块级/嵌套函数上final-on-non-method.mdabstract-and-final-method方法同时是abstractmethod与final自相矛盾abstract-and-final-method.mdabstract-method-in-final-classfinal 类残留未实现的抽象方法abstract-method-in-final-class.md设计上它们彼此咬合例如既抽象又 final的方法是矛盾体抽象方法必须被子类实现、final 方法禁止被重写由独立规则负责而final 类带未实现抽象方法则因为 final 类无法再被继承去补实现而成为一个独立缺陷。把这些规则合起来看ty 对final的静态语义覆盖是成体系的。阅读与深入路径规则正文单一事实来源lint_docs/override-of-final-method.md被include_str!嵌入 Rustdoc规则声明与默认等级diagnostic.rs#L984-L991报告函数与修复逻辑diagnostic.rs#L5159-L5343规则参考页含版本/等级元数据crates/ty/docs/rules.md#L4322-L4358行为规格测试约 1500 行边界场景mdtest/final.md自动生成的快照见 mdtest/snapshots修饰器识别映射function.rs#L198。实践要点回顾不要重写任何被final装饰的方法——包括__init__、属性 getter、classmethod、staticmethod 及重载中的任一签名如需对 final 方法做行为扩展请改在子类中提供新的独立方法名或推动上游放开该契约。若你只是阅读他人代码看到error[override-of-final-method]时应优先把目光投向诊断中标注的超类final装饰器而不是子类本身。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表