ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ruff 生态中 ty 的 unsupported-dynamic-base 规则详解:type() 动态类基类的 MRO 解析限制

Ruff 生态中 ty 的 unsupported-dynamic-base 规则详解:type() 动态类基类的 MRO 解析限制 Ruff 生态中 ty 的 unsupported-dynamic-base 规则详解:type() 动态类基类的 MRO 解析限制【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff本文基于 Ruff 仓库中 ty 类型检查器的规则文档 unsupported-dynamic-base.md,完整讲解unsupported-dynamic-base这条 lint 规则的检测目标、默认级别与启用方式、源码中两个具体的触发路径,以及它与unsupported-base、invalid-base等相邻规则的边界划分,帮助你在阅读type()动态建类代码时准确理解诊断来源。规则做什么unsupported-dynamic-base检测的是通过type()动态创建的类中,ty 无法支持的基类(base)。与静态class语句对应的规则unsupported-base完全等价,只是适用对象不同:后者检查class语句,本规则检查type(Name, (bases,), {})这类动态类定义。规则注册于 diagnostic.rs,摘要为:detects dynamic class bases that are unsupported as ty could not feasibly calculate the classs MRO (检测动态类中不受支持的基类,因为 ty 无法实际计算出该类的 MRO)从注册信息还可以确认:该规则自 ty 0.0.12 起为 stable 状态,默认级别为ignore(即默认关闭)。为什么这是问题如果一个动态创建的类的基类是不受支持的类型(例如type[T]这样的类型对象而非类对象本身),ty 将无法解析该类的方法解析顺序(MRO)。后果是:对该类及其子类的理解能力下降(inferior understanding of your codebase);类型检查行为变得不可预测(unpredictable type-checking behavior)。需要强调的是:这类写法通常不会引发运行时错误——type()在运行时往往能正常执行,问题只在于静态类型检查器无法处理。这正是它与invalid-base(会在运行时抛异常的基类)的核心区别。默认级别与启用方式规则文档明确说明其默认关闭的原因:This rule is disabled by default because it will not cause a runtime error, and may be noisy on codebases that usetype()in highly dynamic ways.即:该规则默认禁用,因为它不会导致运行时错误,且在对type()使用方式高度动态的代码库上可能产生大量噪声。若希望启用,可通过 ty 的配置项rules调整严重级别。根据 ty.schema.json 对rules的定义,合法级别为ignore(禁用)、warn(启用,产生警告诊断)、error(启用,产生错误诊断);配置可写在项目根的ty.toml或pyproject.toml的[tool.ty]段中(配置文件解析逻辑见 metadata.rs,其中ty.toml优先于pyproject.toml):# ty.toml(或 pyproject.toml 中的 [tool.ty]) rules { unsupported-dynamic-base warn }另外注意退出码行为:默认情况下,只要 ty 产生任何 warn 或 error 诊断,进程即以退出码 1 结束;若把该规则设为warn并配合terminal.error-on-warning false,则全部为 warning 时仍以退出码 0 结束。文档示例规则文档给出的最小可复现示例如下:class Base: ... def factory(base: type[Base]) - type: # base has type type[Base], not type[Base] itself return type(Dynamic, (base,), {}) # error: [unsupported-dynamic-base]这里的微妙之处在于:base变量的类型是type[Base](即Base 的某个子类对象),它本身不是类对象。把它当作基类传给type()时,ty 无法为结果类确定 MRO,因此在该表达式上报告unsupported-dynamic-base。源码中的两条触发路径在 dynamic_class.rs 中,可以定位到该诊断的两处上报点。首先需要理解一个前置概念:文件顶部的DynamicClassKind枚举(dynamic_class.rs#L17-L36)区分了两种动态建类入口——type()调用(TypeCall)和types.new_class()调用(NewClass)。两者的诊断消息会根据fn_name自动替换为type()或types.new_class(),也就是说本规则对两种入口都生效,而不仅仅是文档标题强调的type()。路径一:基类是 Protocol在validate_dynamic_type_bases中(dynamic_class.rs#L123-L250),当某个基类能转换为ClassBase::Protocol时,直接报告本规则(dynamic_class.rs#L180-L199):ClassBase::Protocol { if let Some(builder) self .context .report_lint(UNSUPPORTED_DYNAMIC_BASE, diagnostic_node) { let mut diagnostic builder.into_diagnostic(format_args!( Unsupported base for class created via {fn_name} )); ... diagnostic.info(format_args!( Consider using class {name}(Protocol): ... instead )); } }对应测试用例在 unsupported_base_dynamic_type.md 中:from typing import Protocol X type(X, (Protocol,), {}) # error: [unsupported-dynamic-base]诊断提示通过type()创建的类不能是 protocol,并建议改用class X(Protocol): ...静态声明。路径二:基类是类型对象导致 MRO 计算失败当 MRO 求解过程中出现DynamicMroErrorKind::InvalidBases错误时,report_mro_error_kind会对每个非法基类做判别(dynamic_class.rs#L331-L368):if base_type.is_assignable_to(db, env, instance_of_type) { // 基类可赋值给 type 的实例(即形如 type[T] 的类型对象) context.report_lint(UNSUPPORTED_DYNAMIC_BASE, diagnostic_range) // 消息:Unsupported class base, // info:Only class objects or Any are supported as class bases } else if let Some(builder) context.report_lint(INVALID_BASE, diagnostic_range) { // 其他不可接受的基类,报告 invalid-base }判别逻辑是:如果基类类型可赋值给type的实例(例如文档示例中的type[Base]),说明它是一个合法的类对象容器但内容不确定,ty 只能报告unsupported-dynamic-base并提示仅类对象或Any可用作基类;若基类型连type的实例都不是(比如int、字符串等),则改报invalid-base。文档示例type(Dynamic, (base,), {})走的正是这条路径——注意它不是 Protocol,而恰好印证了文档中 type[T]这类基类 的描述。与相邻规则的边界同一 mdtest 文件(unsupported_base_dynamic_type.md)还覆盖了动态类中其他基类的处理,恰好勾勒出本规则的触发边界:场景触发的规则说明基类为type[T]类型对象unsupported-dynamic-base运行时合法,静态无法求 MRO,故默认 ignore基类为Protocolunsupported-dynamic-base动态类不能是 protocol基类为Generic[T]invalid-base建议改用class X(Generic[...])基类为TypedDictinvalid-base建议改用TypedDict(X, {})基类为 Enum 类(经type())invalid-baseEnumMeta需要type()未提供的特殊 dict 属性,建议用Enum(X, [])基类为final类或含成员的 Enumsubclass-of-final-class根本不允许子类化可以看到源码中validate_dynamic_type_bases的分支结构与该表一一对应:Generic/TypedDict走INVALID_BASE,Protocol 走UNSUPPORTED_DYNAMIC_BASE,final 类走SUBCLASS_OF_FINAL_CLASS(dynamic_class.rs#L148-L247)。另外,MRO求解本身的失败(继承环、重复基类、无法得到一致的 MRO)分别由cyclic-class-definition、duplicate-base、inconsistent-mro报告(dynamic_class.rs#L370-L403),均不属于本规则。与静态class语句对应的 unsupported-base 则处理class D(C): ...中C为联合类型等复杂类型的场景,其默认级别为warn(注册见 diagnostic.rs#L530-L537);两者检测逻辑同源,只是入口不同,这也是文档中equivalent tounsupported-base一语的由来。小结unsupported-dynamic-base面向type()/types.new_class()动态建类的基类,检测运行时合法但 ty 无法计算 MRO的情形;默认级别为ignore(stable 于 0.0.12),因为不会造成运行时错误且在高动态代码库上噪声大,可通过rules配置提升为warn或error;源码中该诊断有两条上报路径:Protocol 基类的显式分支,以及 MRO 求解失败后对可赋值给type实例的基类的判定,两条路径的诊断措辞分别为 Unsupported base for class created via ... 与 Unsupported class base;与invalid-base、subclass-of-final-class、inconsistent-mro的分工由 dynamic_class.rs 中的分支结构和 mdtest 用例共同确定。完整规则清单可参阅仓库生成的 规则文档。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表