ARTICLE DETAIL

资讯详情

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

Ruff 类型检查器中的 `__class__` 闭包单元:作用域规则与源码级实现解析

Ruff 类型检查器中的 `__class__` 闭包单元:作用域规则与源码级实现解析 Ruff 类型检查器中的__class__闭包单元作用域规则与源码级实现解析【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffPython 会为定义在类体内的每个方法隐式创建一个名为__class__的闭包单元closure cell供super()等零参数形式在运行时查找当前类。本文以 Ruff 仓库中 ty 类型检查器的 Markdown 测试文档 crates/ty_python_semantic/resources/mdtest/scopes/dunder_class.md 为骨架完整讲解该隐式单元在实例方法、静态方法、类方法、lambda、生成器表达式等各类作用域中的可见性与优先级规则并结合 place_load.rs 等源码说明 ty 如何建模这一隐式加载implicit place load帮助读者掌握类型检查器对 Python 词法作用域边界的处理方式。背景为什么需要隐式的__class__单元Python 编译器会在定义于类体内部的方法所在函数作用域中隐式创建一个名为__class__的闭包单元。这个单元的目的是支持零参数形式的super()当super()在方法内被调用时运行时需要借助该单元获取“当前类”从而沿 MRO 查找父类实现。对于类型检查器而言这意味着__class__必须被当作一个由语法位置决定的隐式绑定来处理——它不是显式赋值产生的名字却会在方法体内解析为定义该方法的那一个类。Ruff 的 ty 语义分析在 place_load.rs 中将其建模为ImplicitPlaceLoad::DunderClass(Definition)/// The implicit __class__ cell for a method, lambda, or generator expression defined directly /// in a class body, e.g.: /// /// py /// class C: /// def method(self): /// return __class__ /// DunderClass(Definitiondb),该源码注释明确给出了与本文档一致的语义隐式单元对直接定义在类体中的方法、lambda、生成器表达式均适用。方法作用域实例方法、静态方法与类方法文档给出的第一个核心断言是隐式__class__单元在实例方法、静态方法staticmethod与类方法classmethod中均可见class C: def method(self) - None: reveal_type(__class__) # revealed: class C staticmethod def static_method() - None: reveal_type(__class__) # revealed: class C classmethod def class_method(cls) - None: reveal_type(__class__) # revealed: class C三种情况下reveal_type(__class__)均被推断为class C即定义该方法的类本身。需要说明的是reveal_type与# revealed:/# error:断言语法属于 ty 的 Markdown 测试框架mdtest该框架约定见 crates/ty_test/README.md# revealed:必须与reveal_type参数的推断类型显示形式完全一致# error: [rule-code]则断言某行会产生指定规则码的诊断。从源码看ty 通过语义索引查询“当前作用域所属的类定义”来获得该单元place_load.rs 中的 dunder_class_cell_definition 先调用index.class_definition_of_method(current_scope)若当前文件作用域是某个方法体则直接返回该方法的类定义解析路径decide_resolution_pathplace_load.rs在符号名恰为__class__且存在类定义时转入DunderClassSource节点产出ImplicitPlaceLoad::DunderClass这一隐式来源类型推断阶段infer/builder.rs将该来源求值为original_class_type(db, definition)即原始类类型本身最终得到class C。这与文档断言完全一致且实现了class_definition_of_method查询被 types/function.rs 等多个模块复用可确认是 ty 中语义索引的标准能力。Lambda 作用域捕获与参数遮蔽直接写在类体内的 lambda 同样能捕获该单元但 lambda 的参数优先于隐式单元class C: lambda_method lambda: reveal_type(__class__) # revealed: class C shadowed lambda __class__: reveal_type(__class__) # revealed: Unknown第一个 lambda 没有名为__class__的参数因此在 lambda 体内__class__回退到隐式单元推断为class C第二个 lambda 声明了参数__class__按 Python 词法规则函数参数属于该 lambda 作用域的本地绑定优先级高于外层闭包单元因此reveal_type(__class__)的推断结果退化为Unknown参数类型未标注时无法确定。从实现角度dunder_class_cell_definition中 place_load.rs 专门处理了NodeWithScopeKind::Lambda(_)分支仅当当前作用域是 lambda 且其父作用域是类体时才返回父类定义而名称解析的LocalSource优先于DunderClassSourceplace_load.rs 中先检查局部符号再检查隐式单元由此在机制上保证了“参数遮蔽单元”。生成器表达式作用域惰性求值下的捕获差异生成器表达式在类体内的情况是文档中最细致的一节它区分了三类位置class C: values ( reveal_type(__class__) # revealed: class C for _ in range(1) ) first_iterable ( value for value in ( __class__, # error: [unresolved-reference] ) ) eager_comprehension [ __class__ # error: [unresolved-reference] for _ in range(1) ]规则背后的 Python 语义是生成器表达式体是惰性求值的values中的表达式体在生成器真正被迭代时才执行此时其函数作用域位于类体内因而能捕获隐式单元推断为class C第一个可迭代对象是急切求值的first_iterable中位于for之前的迭代源(__class__,)在类体上下文里立即执行而类体作用域此时还拿不到该单元产生[unresolved-reference]列表推导等急切推导体同样在类体作用域立即求值访问__class__同样报[unresolved-reference]。实现侧与 lambda 的处理方式一致dunder_class_cell_definition在 place_load.rs 中同样包含NodeWithScopeKind::GeneratorExpression(_)分支仅当生成器表达式的父作用域是类体时才提供隐式单元急切求值的部分不属于该惰性作用域因而走普通名称解析最终以PlaceLoadFailure::NotFound结束并报告[unresolved-reference]失败枚举见 place_load.rs。类体与默认参数不可访问的位置与前两节相对文档明确指出单元在类体直接求值和方法默认参数求值中不可用class C: __class__ # error: [unresolved-reference] def method( self, value__class__, # error: [unresolved-reference] ) - None: ...原因在于类体本身并不拥有__class__单元——单元是编译器为方法函数创建闭包时注入的方法默认参数在类体执行阶段函数对象创建时求值也早于进入方法函数作用域。因此两处访问都落入普通名称解析并失败。这与dunder_class_cell_definition只在“方法体 / 类体内的 lambda / 类体内的生成器表达式”三类作用域上返回定义的事实一致类体作用域is_class_body_scopeplace_load.rs本身并不会触发DunderClass隐式来源。遮蔽规则全局、本地与显式 global 的优先级文档进一步验证了名称优先级顺序——隐式单元高于同名全局变量但本地绑定与显式global声明高于单元__class__ int class D: def implicit(self) - None: reveal_type(__class__) # revealed: class D def local(self) - None: __class__ str reveal_type(__class__) # revealed: class str def explicit_global(self) - None: global __class__ reveal_type(__class__) # revealed: class int三个分支的结论implicit模块级虽有__class__ int但方法内的隐式单元优先级更高推断为class Dlocal方法体内对__class__的赋值使其成为本地绑定推断为class strexplicit_globalglobal __class__声明将名字转发到模块作用域读取到模块级绑定int。实现上decide_resolution_path 先检查符号是否为本地绑定symbol_is_local是则直接返回本地来源或UnboundLocal不会走到隐式单元仅当本地无此符号、且名字是__class__且存在类定义时才进入DunderClassSource。同时DunderClassSource节点还携带了剩余的外层作用域enclosing_scopes其后续步骤仍可继续解析显式 global、模块隐式全局与内建从而在机制上支持“单元优先、global 兜底”的优先级链。已知限制尚未建模的词法边界文档明确列出当前实现的局限隐式单元仅在直接方法体与直接定义于类体中的惰性作用域中被建模以下合法用法暂时无法解析。嵌套函数作用域class C: def method(self) - None: def nested() - None: # TODO: This should reveal class C without an error. # error: [unresolved-reference] # revealed: Unknown reveal_type(__class__)运行时__class__单元会通过闭包链向嵌套函数传播但当前 ty 只在“直接”的方法体上建立隐式来源dunder_class_cell_definition仅检查当前作用域是否为方法体或类体内的 lambda/生成器表达式嵌套的nested不满足该条件因此报[unresolved-reference]revealed: Unknown。源码注释TODO表明这是已知缺口。类型别名注解作用域在 Python 3.12 下测试通过[environment]中的python-version 3.12指定crates/ty_test/README.md 说明 toml 代码块用于测试配置[environment] python-version 3.12class C: # TODO: This should resolve to C without an error. type Alias __class__ # error: [unresolved-reference] # TODO: This should resolve to C without an error. type GenericAlias[T] __class__ # error: [unresolved-reference]泛型方法约束[environment] python-version 3.12class C: # TODO: The bound should resolve to C without an error. def methodT: __class__ - None: ... # error: [unresolved-reference]延迟求值的方法注解Python 3.14 开始延迟注解求值普通方法注解本应能访问单元[environment] python-version 3.14class C: def method( self, # TODO: This should resolve to C without an error. value: __class__, # error: [unresolved-reference] # TODO: This should resolve to C without an error. ) - __class__: # error: [unresolved-reference] raise NotImplementedError以上限制共同指向一个核心事实ty 目前将隐式__class__单元绑定在“最内层的直接方法/lambda/生成器表达式作用域”上尚未把它表示在正确的词法作用域边界即能沿闭包链与注解延迟求值传播的位置因此这些用例统一报告[unresolved-reference]并保留TODO等待后续实现。如何运行这些测试这些 Markdown 文件本身就是 ty 的测试用例由 crates/ty_python_semantic/tests/mdtest.rs 集成测试执行凡是位于resources/mdtest/目录下的 Markdown 文件都会被当作测试套件见 crates/ty_python_semantic/resources/README.md。测试通过# revealed:与# error:注释断言诊断结果二者分别验证推断类型与错误规则码格式细节见 crates/ty_test/README.md 的 Assertions 一节。如需只运行__class__相关的用例可借助仓库脚本 crates/ty_python_semantic/mdtest.py一个基于 rich/watchfiles 的 Markdown 测试运行器传入路径过滤器例如scopes/dunder_class.md该脚本会先编译ty_python_semantic包的mdtest测试目标再对匹配的 Markdown 文件执行并输出结果也可通过--no-lockfile-upgrades、--no-snapshot-updates控制锁文件升级与内联快照更新行为。修改文档或源码后运行器会监视变更并自动重跑相关用例适合在开发类型检查器时使用。小结__class__隐式闭包单元是 Python 类语义中容易被忽略却影响面广泛的机制。本文档及其背后的 ty 实现给出了清晰的行为矩阵可见直接方法体实例/静态/类方法、类体内的 lambda、惰性求值的生成器表达式体不可见类体直接求值、方法默认参数、生成器表达式的第一个可迭代对象、急切推导体优先级本地绑定 隐式单元 同名全局 内建显式global声明会绕过单元待完善嵌套函数、类型别名注解、泛型约束与延迟注解中的合法用法。这些规则在 ty 中通过ImplicitPlaceLoad::DunderClass、dunder_class_cell_definition查询与DunderClassSource解析节点逐层落地与本文档中的每个revealed:/error:断言一一对应是理解 Ruff 类型检查器作用域建模的最佳切入点之一。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表