
ty 类型检查器规则解析invalid-typed-dict-statement与 TypedDict 类体语句约束【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffTypedDict是 Python 中用于描述结构固定的字典的核心类型工具但其类定义体的语法约束十分严格除字段注解声明外几乎不允许出现任何其他语句。在 GitHub 精选项目 ru/ruffAstral 出品、基于 Rust 的 Python 工具链的实验性类型检查器ty中invalid-typed-dict-statement规则专门负责检测TypedDict类体内的非法语句。阅读本文后你将理解该规则捕获的错误形态、TypedDict类体真正的合法语法边界、底层源码的判定实现以及修复此类错误的实践方法。规则概述它检测什么规则invalid-typed-dict-statement默认错误级别error检测的是TypedDict类体中出现的、除注解声明annotated declarations以外的任何其他语句。官方 lint 文档定义如下Detects statements other than annotated declarations inTypedDictclass bodies.也就是说当类型检查器ty看到一个基于类class-based语法声明的TypedDict时它会逐条检查类体内的语句任何不属于字段注解声明的语句——例如方法定义、带赋值默认值的语句、普通表达式等——都会被标记为该规则下的诊断。原文档给出的最小触发示例from typing import TypedDict class Foo(TypedDict): def bar(self): # error: [invalid-typed-dict-statement] pass在这个例子中class Foo(TypedDict)内的def bar(self)是一个方法定义不是字段注解声明因此触发invalid-typed-dict-statement。为什么这是错误的运行时的真相该规则不是无意义的风格洁癖其背后是对 Python 运行时行为的精确建模。原文档给出的核心理由是TypedDictclass bodies arent allowed to contain any other types of statements. For example, method definitions and field values arent allowed. None of these will be available on instances of theTypedDict at runtime (asdictis the runtime class of all TypedDictinstances).翻译并展开理解在运行时所有TypedDict实例的真实类都是dict。TypedDict语法在运行期只是调用typing.TypedDict的元类机制来收集字段注解生成一个dict的子类而并不是生成一个带有方法、自定义属性行为的新型对象。因此在TypedDict类体中定义的方法不会出现在任何实例上——你拿到的是一个普通字典访问foo.bar()必然失败静态层面的方法存在是一种幻觉。同理在类体中给字段赋一个具体值例如x: int 3该值同样不会成为运行期字段的默认值运行时这个默认值也不会以预期方式生效。既然这些语句在运行期不存在、在类型层面又是类型检查器无法消费的杂质那么把它们写入TypedDict类体就几乎可以断定是代码作者的误解或笔误类型检查器应当尽早报错而不是放任这种误导性的代码。合法语法边界TypedDict 类体到底能写什么要理解该规则的允许集合需要回到官方 typing 规范。ty 源码在实现该校验时直接引用了 typing 规范中 class-based syntax 一节的原文注释见 post_inference/typed_dict.rsThe body of the class definition defines the items of theTypedDicttype. It may also contain a docstring or pass statements (primarily to allow the creation of an emptyTypedDict). No other statements are allowed, and type checkers should report an error if any are present.由此可以归纳出TypedDict 类体的完整合法语句清单语句类型是否允许说明注解赋值name: TypeAnnAssign无值✅ 允许这就是字段声明本身是整个语法的目的所在如name: str、id: NotRequired[int]注解赋值但带值name: Type value❌ 禁止ty 会报 TypedDict item cannot have a valuepass✅ 允许主要用于声明空的TypedDict文档字符串docstring✅ 允许类级说明性文字属于表达语句Expr中的字符串字面量特例...省略号字面量✅ 允许ty 将其解释为等价于pass这是 ty 的非标准但常见的扩展if语句✅ 允许有条件允许但其内部所有语句必须递归地通过同样的校验方法定义def❌ 禁止ty 会报 TypedDict class cannot have methods其余一切语句❌ 禁止ty 会报 invalid statement in TypedDict class body值得注意的是if语句是显式允许的但只允许其包含的仍是合法语句。这在实践中通常对应版本条件或TYPE_CHECKING场景下按需声明字段的模式例如import sys from typing import TypedDict class PlatformInfo(TypedDict): name: str if sys.version_info (3, 10): # 合法if 会被放行但其内部语句必须同样是合法声明 pep_604_marker: bool而如果把一个方法放进if分支例如from typing import TypedDict class Bad(TypedDict): if True: def helper(self): # error: [invalid-typed-dict-statement] passty 的校验函数会递归进入if的body与所有elif/else分支逐条执行同一套判定逻辑最终仍然命中该规则。合法的 TypedDict 长什么样与触发示例相对下面展示完全合法的 class-basedTypedDict这些写法不会触发本规则from typing import NotRequired, Required, TypedDict class Movie(TypedDict): A single movie entry. # 1. 文档字符串允许 title: str # 2. 纯注解声明字段声明 year: int rating: NotRequired[float] # 3. 用 NotRequired/Required 表达可选/必填 ... class Empty(TypedDict): pass # 4. pass 用于创建空的 TypedDict核心原则一句话类体中的每一条语句要么是字段类型声明name: Type不带值要么是为了支撑这一声明形态而存在的结构占位docstring、pass、...、纯声明式的if分支。源码级实现ty 如何逐条判定该诊断的判定逻辑位于ty_python_semanticcrate 中。规则声明定义在 diagnostic.rs其对应的规则注册、默认级别等元数据由宏派生而真正的类体校验在类定义推断的后处理阶段触发。触发入口在 post_inference/typed_dict.rs 中当一个基于类语法声明的TypedDict完成主体推断后会依次调用类体校验、字段重定义校验与开放性校验其中类体校验的入口为fn validate_typed_dict_class_body(context: InferContext_, _, class_node: ast::StmtClassDef) { validate_typed_dict_class_body_statements(context, class_node.body); }逐条匹配的判定函数核心判定在validate_typed_dict_class_body_statements它对类体语句列表做一次模式匹配逻辑可以还原如下ast::Stmt::AnnAssign注解赋值——放行但有附加检查如果该注解声明携带了值value非空则同样上报INVALID_TYPED_DICT_STATEMENT诊断文案为TypedDict item cannot have a value。因为TypedDict字段的默认值在运行期不成立这是最常见的误解写法之一。ast::Stmt::Pass——放行用于支撑空的TypedDict。ast::Stmt::If——放行外层递归校验内部对if的body和每个elif_else_clause的body递归调用同一校验函数确保条件分支中不会夹带非法语句。ast::Stmt::Expr——仅当是字符串字面量docstring或省略号字面量时放行docstring 是规范的合法内容把...当作pass使用则是 ty 采纳的非标准但常见的扩展源码注释原文为As a non-standard but common extension, we also interpret...as equivalent topass允许写出class Foo(TypedDict): ...这类简洁空类。其余一切语句——上报错误在默认_ {}分支统一构造诊断。ty 会区分报错文案若是方法定义FunctionDef输出TypedDict class cannot have methods精准点出类里不能有方法这一最常见误用其他非法语句输出invalid statement in TypedDict class body并在诊断上附加一条 infoOnly annotated declarations (name: type) are allowed.引导用户把类体收敛为纯字段声明。可以看到ty 对这条规则的实现与 typing 规范文本严格对齐甚至把规范的允许/禁止集合直接以注释形式固化在代码中诊断信息也区分了方法与其他杂语句两类高频场景帮助用户快速定位意图错误。与周边 TypedDict 规则的协同invalid-typed-dict-statement只是 ty 围绕TypedDict提供的一组细粒度规则中的一员。在 lint_docs 目录 中可以看到它们各自聚焦不同的校验切面值得一并理解以形成完整的TypedDict使用边界invalid-typed-dict-header校验TypedDict的类头例如自定义元类typing 规范禁止TypedDict使用自定义 metaclass、**展开基类等**展开会使 ty 无法静态判定字段是必填还是可选。invalid-typed-dict-field校验字段声明的兼容性——子类不能以不兼容的方式重定义继承来的字段否则会破坏TypedDict继承想保证的子类型关系。本规则invalid-typed-dict-statement校验类体内语句形态保证类体只承载字段声明这一种语义。三者分别管住类头怎么写、类体里能放什么语句、字段怎么继承重写配合missing-typed-dict-key、invalid-key、isinstance-against-typed-dict、mismatched-type-name等规则构成了 ty 对TypedDict声明的体系化静态检查。测试与验证该规则的预期行为在类型检查器测试语料mdtest中有直接覆盖见 typed_dict.md 及其生成的快照。测试会渲染出形如# error: [invalid-typed-dict-statement]的期望标记并与实际诊断输出做快照比对确保规则文案、诊断位置与分类的稳定性。规则说明的权威汇总默认级别、引入版本等元数据由工具链自动生成在 rules.md 中文件头部亦注明该类文档由cargo dev generate-all生成修改应编辑 lint 声明源文件而非文档本身。实战修复指南当你在使用 ty或其 IDE 集成检查代码时看到invalid-typed-dict-statement可以按下面的思路处理场景一在 TypedDict 里写了方法from typing import TypedDict class Config(TypedDict): host: str port: int def display(self) - str: # error: [invalid-typed-dict-statement] return f{self[host]}:{self[port]}TypedDict实例在运行期就是dict方法不会存在于任何实例上。正确的做法是把辅助逻辑抽成模块级函数或用普通dataclass/ 类承载行为让TypedDict只描述字典形状from typing import TypedDict class Config(TypedDict): host: str port: int def display(config: Config) - str: return f{config[host]}:{config[port]}场景二给字段写默认值from typing import TypedDict class Config(TypedDict): host: str port: int 8080 # error: [invalid-typed-dict-statement] TypedDict item cannot have a value字典类在运行期是dict这个默认值不会参与任何字典创建过程属于对TypedDict语义的误解。若确实需要默认值应在创建实例处显式补齐或用NotRequired表达可选性from typing import NotRequired, TypedDict class Config(TypedDict): host: str port: NotRequired[int] # 合法纯类型声明表达该键可以缺席 default_port 8080场景三空 TypedDict 的正确写法若确实需要声明一个空的TypedDict请使用pass、...或 docstring 而非空语句之外的任何内容from typing import TypedDict class Opaque(TypedDict): ...场景四局部抑制如果某段代码确实需要临时豁免该诊断可以在对应行尾使用 ty 的ty: ignore注释并带规则名例如from typing import TypedDict class Foo(TypedDict): def bar(self): # ty: ignore[invalid-typed-dict-statement] passty 的抑制注释机制支持按规则精确忽略从而避免宽泛的无差别屏蔽仓库内另有专门针对不写规则名的 blanket ignore的警告规则参见blanket-ignore-comment。不过从代码健康度看更推荐优先用前三种结构性修复消除误用而不是依赖抑制。小结invalid-typed-dict-statement精准地落实了 typing 规范对 class-basedTypedDict类体的语法约束类体只能包含字段注解声明外加 docstring、pass、...与纯声明式的if分支其余一切方法、字段默认值等都会被拒绝。这一约束并非教条——在运行期所有TypedDict实例都是普通dict类体中的方法定义与字段值本就不存在ty 的源码实现既复述了规范原文又为方法、带值注解等高频误区提供了针对性诊断文案。理解并善用这条规则能让你的TypedDict声明始终贴合其真实运行时语义避免写出看起来面向对象、运行起来是字典的误导性代码。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考