ARTICLE DETAIL

资讯详情

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

ty 类型检查器 lint 规则解析:possibly-missing-submodule 如何捕获“可能未导入的子模块”访问

ty 类型检查器 lint 规则解析:possibly-missing-submodule 如何捕获“可能未导入的子模块”访问 ty 类型检查器 lint 规则解析possibly-missing-submodule 如何捕获“可能未导入的子模块”访问【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读本文围绕 ruff 仓库内嵌的 ty 类型检查器ty_python_semanticcrate中的possibly-missing-submodule规则展开讲解它如何识别通过父模块访问可能尚未被导入的子模块这类隐患即import a之后直接使用a.b而a.b从未被显式导入、也没有在__init__.py中被重新导出时运行期将触发AttributeError。读完本文你将掌握该规则的触发条件、背后的模块解析原理、IDE 中的修复方式以及如何在__init__.py与 stub 文件中正确组织子模块导出让这类错误在静态检查阶段就被拦截。规则定位与定义possibly-missing-submodule是 ty 类型检查器提供的一组 lint 规则之一它被声明于 crates/ty_python_semantic/src/types/diagnostic.rs 中其完整文档由 crates/ty_python_semantic/resources/lint_docs/possibly-missing-submodule.md 通过include_str!嵌入declare_lint! { #[doc include_str!(../../resources/lint_docs/possibly-missing-submodule.md)] pub(crate) static POSSIBLY_MISSING_SUBMODULE { summary: detects accesses of submodules that may not be available as attributes on their parent module, status: LintStatus::stable(0.0.23), default_level: Level::Warn, } }从声明中可以提取出该规则的关键元数据元数据值规则名possibly-missing-submodule功能摘要检测可能无法作为父模块属性访问的子模块访问默认级别warn警告引入版本0.0.23标记为 stable它对应的说明文档位于 crates/ty/docs/rules.md 的possibly-missing-submodule一节同样标注了默认级别为warn。由于默认级别是警告而非错误它不会阻断类型检查流程但会在 IDE 与命令行输出中明确提醒开发者注意潜在的子模块访问问题。它与同族规则的关系在diagnostic.rs中possibly-missing-submodule与一组possibly-*系列规则并列包括possibly-missing-attribute可能缺失的属性、possibly-missing-import可能缺失的导入、possibly-unresolved-reference可能未定义的名称等。这组规则共同体现了 ty 的保守策略当信息不足、无法确定某个名称是否真的不存在时ty 选择用可能缺失级别的诊断提示而不是直接报错——既避免误报又不错过潜在缺陷。本规则专门聚焦于模块 → 子模块这一条访问路径。规则行为它检查什么原文档明确给出了该规则的用途Checks for accesses of submodules that might notve been imported.即当代码通过父模块去访问一个子模块属性而该子模块可能并没有被导入时规则触发警告。一个典型的触发示例来自原文档import html # AttributeError: module html has no attribute parser html.parser # error这里的语义是html是一个包packagehtml.parser是它的一个子模块文件。仅执行import htmlPython 解释器只会加载html/__init__.py并不会自动把html.parser挂载为html的属性。因此运行时html.parser会抛出AttributeError: module html has no attribute parser——除非某处已经显式执行过import html.parser或html/__init__.py内部有from . import parser。ty 之所以能报出这条警告是因为它拥有模块解析module resolution能力在静态检查阶段ty 能看到html.parser这个模块在文件系统中真实存在可以被解析到但它并不一定已经被导入并绑定为html的属性于是给出可能未导入的保守诊断。为什么这是坏的写法Python 子模块导入语义原文档的 Why is this bad? 一节解释了底层机制When moduleahas a submoduleb,import aisnt generally enough to let you accessa.b. You either need to explicitlyimport a.b, or else you need the__init__.pyfile ofato includefrom . import b. Without one of those,a.bis anAttributeError.把这段话翻译成可操作的三条规则仅import a不够导入包a只执行其__init__.py子模块a.b不会自动成为a的属性显式import a.b可以修复一旦代码某处显式导入了a.b模块对象b就会被挂到包a上后续a.b即可访问__init__.py内from . import b也可以修复这是包作者在__init__.py中主动把子模块作为属性重新导出re-export的标准做法。如果没有满足上述任一条件a.b就是一次注定失败的属性访问错误直到运行期才会暴露。而 ty 的目标就是把这个运行期错误提前到静态检查阶段发现。值得强调的是一般不够isntgenerallyenough这一措辞——Python 存在一些让子模块意外可用的边角情况例如其他模块已经导入过a.b模块缓存sys.modules中的副作用这正是该规则使用可能might not而非一定没有的原因。典型示例与修复触发警告的写法import html html.parser # warning: Submodule parser might not have been importedty 在html.parser处输出类似如下的诊断来自 ty_server 的端到端测试快照 e2e__code_actions__code_action_possible_missing_submodule_attribute.snapSubmodule parser might not have been imported help: Consider explicitly importing html.parser诊断同时给出修复建议显式导入html.parser。修复方式一显式导入子模块import html import html.parser # 显式导入html.parser 现在可安全访问 html.parser修复方式二在包的__init__.py中重新导出由包作者在__init__.py中主动声明子模块# html/__init__.py from . import parser之后外部代码import html即可安全使用html.parser。修复方式三在 IDE 中快速忽略ty_server 为这条规则提供了代码动作code action。从测试快照可以看到IDE 会提供一条名为Ignore possibly-missing-submodule for this line的 quickfix作用是在行尾追加# ty: ignore[possibly-missing-submodule]注释。该机制与 ruff 生态中熟知的# noqa思路一致适用于此处确实故意如此写的少数场景import html html.parser # ty: ignore[possibly-missing-submodule]底层实现属性查找失败后的模块解析回退要真正理解这条规则的判定逻辑需要深入 crates/ty_python_semantic/src/types/infer/builder.rs 中属性推断的实现。核心逻辑位于lookup_result.unwrap_or_else(...)的LookupError::Undefined(_)分支约 builder.rs#L10800-L10832其判定流程可以拆解为属性查找失败对value.attr做成员查找member_lookup时返回LookupError::Undefined说明value类型上并不存在名为attr的属性确认接收者是一个包模块检查value_type是否是Type::ModuleLiteral(module)且该模块的kind是包module.kind(db).is_package()。只有包才可能拥有子模块普通模块不可能因此此步先行过滤构造可能的子模块全名把属性名attr_name作为相对子模块名拼接到父模块名之后得到maybe_submodule_name例如htmlparser→html.parser尝试解析该子模块调用resolve_module(db, ImportingFile::File(...), maybe_submodule_name)。resolve_module来自 ty 的模块解析器ty_module_resolvercrate。如果解析成功说明文件系统/stub 环境中确实存在这个子模块文件它只是没有被导入到父模块的属性上——这正是本规则命中report_lint(POSSIBLY_MISSING_SUBMODULE, attribute)的时刻若解析失败说明连子模块本身都不存在则不属于可能未导入而是真正的属性缺失交由其他路径如unresolved-attribute处理。从这段源码可以提炼出本规则的两个核心前提二者缺一不可子模块必须真实存在resolve_module能解析到父模块属性上却查不到它LookupError::Undefined。如果子模块根本不存在报的就不是可能未导入而是普通的不存在属性如果子模块已通过某种方式绑定为父模块属性查找就不会失败规则自然静默。诊断信息的构造命中后源码通过builder.into_diagnostic(format_args!(Submodule{attr_name}might not have been imported))构造主消息并通过diag.help(format_args!(Consider explicitly importing{maybe_submodule_name}))附加修复提示。这也解释了为什么快照中的帮助文本总是形如Consider explicitly importinghtml.parser——它直接使用了第 3 步拼接出的完整模块名。规则触发与不触发的边界导入约定的影响哪些子模块会被挂到父模块属性上取决于导入约定ty 在 crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md 中记录了它对若干非标准但广泛存在的导入约定的处理策略这些约定直接决定了possibly-missing-submodule何时触发、何时不触发from . import b是显式重新导出在__init__.py或 stub 文件__init__.pyi中写from . import bty 视其为对b的显式重新导出因此import package之后package.b是合法属性规则不触发# mypackage/__init__.py from . import imported # imported 成为 mypackage 的属性 # main.py import mypackage reveal_type(mypackage.imported.X) # revealed: int —— 可正常访问未导出的子模块触发规则同目录下存在另一个子模块fails.py但__init__.py没有导出它于是访问mypackage.fails触发警告# error: [possibly-missing-submodule] Submodule fails might not have been imported reveal_type(mypackage.fails.Y) # revealed: Unknownty 对无法确定的对象给出Unknown类型同时抛出规则诊断——既如实反映类型未知又提示这可能是个导入遗漏。绝对导入等价的子模块同样被视为导出如果__init__.py中写的是绝对导入且恰好导入了自己的子模块等价于from . import bty 同样视为重新导出规则不触发唯一的退出机制是重命名导入如from . import b as c。相关细节与测试用例可参见 nonstandard_conventions.md 的 AbsolutefromImport of Direct Submodule in__init__ 一节。测试覆盖stub 与非 stub 双版本nonstandard_conventions.md中的每个场景都提供了 stub__init__.pyi*.pyi与非 stub__init__.py*.py两套测试原因正如文档开头所述我们既关心符号是否被定义也关心它是否被重新导出。这意味着possibly-missing-submodule的行为在两种环境下都经过了验证。运行期行为验证mdtest 快照测试规则的行为在 crates/ty_python_semantic/resources/mdtest/attributes.md 中以快照断言的方式固化测试代码使用reveal_type观察类型并断言诊断输出。例如import foo # snapshot: possibly-missing-submodule reveal_type(foo.bar) # revealed: Unknown对应快照warning[possibly-missing-submodule]: Submodule bar might not have been imported -- src/foo_importer.py:4:13 | 4 | reveal_type(foo.bar) # revealed: Unknown | ^^^^^^^ help: Consider explicitly importing foo.bar这段快照同时验证了两件事类型层面foo.bar的类型被揭示为Unknown——ty 不假装知道它的类型诊断层面发出warning[possibly-missing-submodule]并附带指向属性位置的精确 span 和显式导入建议。同样的断言在baz.bar场景attributes.md中重复出现说明该规则与具体的模块命名无关适用于任意的包 未导出子模块组合。在 IDE 与命令行中的实际呈现possibly-missing-submodule不只存在于类型检查器的内部逻辑中它已经完整接入 ty_server即 ty 的语言服务端。端到端测试快照 e2e__code_actions__code_action_possible_missing_submodule_attribute.snap 展示了完整的 IDE 集成形态诊断对象code: possibly-missing-submodule消息为Submoduleparsermight not have been imported附带help: Consider explicitly importinghtml.parser严重级别快照中severity: 2对应警告级别与diagnostic.rs中default_level: Level::Warn的声明一致代码动作提供Ignore possibly-missing-submodule for this linequickfix在对应行尾插入# ty: ignore[possibly-missing-submodule]且isPreferred: false不作为首选修复避免掩盖真正的导入遗漏文档链接诊断携带指向ty.dev/rules#possibly-missing-submodule的文档地址方便开发者从 IDE 直接跳转阅读规则说明。这意味着开发者在使用支持 ty 的编辑器中编写代码时可以即时看到此类警告并一键选择显式导入修复或忽略本行。小结何时该听这条规则的劝告possibly-missing-submodule是 ty 在模块解析与属性查找两条路径交汇处设计的一道安全网。它传递的核心经验可以总结为三条不要假设import 包就自动拥有其所有子模块属性——Python 不会为你隐式导入子模块除非__init__.py主动导出两种标准修复方式在访问方显式import a.b或在包内__init__.py中写from . import b完成重新导出警告不是误报ty 只有在resolve_module确认子模块真实存在、却未挂载为父模块属性的情况下才报此规则同时给出可直接执行的导入建议与 IDE 快速修复入口。对库作者而言遵守这条规则意味着在__init__.py中明确声明你的公开子模块接口对普通使用者而言它能把原本要等到运行期才暴露的AttributeError提前变成编辑器里的一行清晰警告。参考资源规则文档原文crates/ty_python_semantic/resources/lint_docs/possibly-missing-submodule.md规则声明与元数据crates/ty_python_semantic/src/types/diagnostic.rs#L939-L946核心判定逻辑crates/ty_python_semantic/src/types/infer/builder.rs#L10800-L10832导入约定与边界测试crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md行为快照测试crates/ty_python_semantic/resources/mdtest/attributes.mdIDE 集成端到端快照crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_possible_missing_submodule_attribute.snap用户文档规则页crates/ty/docs/rules.md#L4583【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表