ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CPython 修复:`inspect.signature()` 现在可解析类型别名与类型参数的惰性求值器

CPython 修复:`inspect.signature()` 现在可解析类型别名与类型参数的惰性求值器 CPython 修复inspect.signature()现在可解析类型别名与类型参数的惰性求值器【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本指南围绕 CPython 仓库中的一条 Core and Builtins 变更Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-55-18.gh-issue-151665.82fmzx.rst展开编译器为类型别名与类型参数生成的惰性求值器lazy evaluator此前会导致inspect.signature()抛出ValueError本次修复后可以正常解析其签名为(format1, /)。读完本文你将理解这些求值器在解释器内部是如何被编译生成、为何曾破坏内省以及如何通过测试用例验证该行为。背景PEP 695 语法背后的惰性求值器现代 Python 允许用声明式语法定义类型别名与类型参数例如type Alias int # 类型别名 def f[T: int int, **P int, *Ts int](): ... # 带 bound/default 的类型参数 def g[U: (int, str)](): ... # 带约束constraints的类型参数为了做到定义时不求值、使用时才求值CPython 编译器会为以下实体分别生成一个独立的闭包函数惰性求值器Lib/annotationlib.py 中call_evaluate_function的文档注释明确说明了这一点Evaluate functions are normally generated for the value of type aliases and the bounds, constraints, and defaults of type parameter objects.也就是说每个TypeAliasType和每个TypeVar/ParamSpec/TypeVarTuple对象上都挂载着一组求值函数例如Alias.evaluate_value—— 求类型别名的值T.evaluate_bound—— 求类型参数的边界boundT.evaluate_default—— 求类型参数的默认值U.evaluate_constraints—— 求类型参数的约束元组。这些函数接受一个format参数用来指定求值结果的表示形式。Lib/annotationlib.py 中Format枚举定义了四种格式成员值含义Format.VALUE1返回实际对象值Format.VALUE_WITH_FAKE_GLOBALS2内部专用配合假全局命名空间求值Format.FORWARDREF3返回包装了前向引用的ForwardRef对象Format.STRING4返回源码字符串形式annotationlib.call_evaluate_function(evaluate, format, *, ownerNone)Lib/annotationlib.py是调用这类求值函数的标准入口它内部转交call_annotate_function处理NotImplementedError回退与 STRING 格式的字符串化。问题.format参数名曾导致ValueError在本次修复之前对上述求值器调用inspect.signature()会抛出ValueError。根因在于编译器生成这些闭包时使用的参数命名。查看 Lib/test/test_type_params.py 中针对 gh-151665 的回归测试注释gh-151665: the .format parameter of compiler-generated evaluators used to break inspect.signature(). It should show up as format.从源码结构看编译器内部把这些求值函数的形参命名为.format一个以点号开头、并非合法标识符的名字以避免与注解表达式里用户自己的format名字在符号表中冲突但inspect.signature()在解析函数签名时需要合法的参数标识符遇到.format这种名字时便无法构造Signature最终以ValueError失败。修复编译器在符号表层面重命名形参修复位于编译器代码生成阶段 Python/codegen.c 的codegen_rename_annotations_format_paramPython/codegen.c。该函数在求值闭包编译完成后、组装成闭包对象之前把co_localsplusnames代码对象的局部变量名表的第一个名字从.format替换为format// We want the parameter to __annotate__ to be named format in the // signature shown by inspect.signature(), but we need to use a // different name (.format) in the symtable; if the name // format appears in the annotations, it doesnt get clobbered // by this name. This code is essentially: // co-co_localsplusnames (format, *co-co_localsplusnames[1:])这样设计的两阶段命名策略非常关键符号表阶段用.formatcodegen_setup_annotations_scope见 Python/codegen.c以.format为参数名进入注解作用域避免与用户注解中出现的format名字互相覆盖名字不会在符号表层面被劫持字节码组装后重命名为formatcodegen_rename_annotations_format_param把co_localsplusnames首项替换为_Py_ID(format)其余名字原样保留。此时用户代码已经编译完毕改名不会再影响语义却能让inspect.signature()正确展示参数名。codegen_finish_annotations_scopePython/codegen.c在生成普通__annotate__函数时同样调用该重命名函数而类型参数求值器的路径codegen_type_param_bound_or_defaultPython/codegen.c也会在codegen_make_closure之前调用它。此外该函数还在MAKE_FUNCTION_DEFAULTS下为闭包附加了默认参数(1,)即Format.VALUE——这正是最终签名中format1的来源调用求值器时不传参等价于请求求值为真实对象值。验证回归测试如何断言新行为Lib/test/test_type_params.py 中的TestEvaluateFunctions.test_signature覆盖了全部求值器种类def test_signature(self): # gh-151665: the .format parameter of compiler-generated evaluators # used to break inspect.signature(). It should show up as format. type Alias int def f[T: int int, **P int, *Ts int](): pass T, P, Ts f.__type_params__ def g[U: (int, str)](): pass U, g.__type_params__ cases [ Alias.evaluate_value, T.evaluate_bound, T.evaluate_default, P.evaluate_default, Ts.evaluate_default, U.evaluate_constraints, ] for case in cases: with self.subTest(casecase): sig inspect.signature(case) self.assertEqual(str(sig), (format1, /)) param, sig.parameters.values() self.assertEqual(param.name, format) self.assertIs(param.kind, inspect.Parameter.POSITIONAL_ONLY)断言要点有三个str(sig) (format1, /)签名只有一个位置参数format默认值为1即annotationlib.Format.VALUE/表示仅限位置传递positional-only参数名是format不再是引发异常的.formatparam.kind是POSITIONAL_ONLY与编译器生成的__annotate__/evaluate 函数约定一致——求值函数只接收一个位置参数format。同一测试类的test_generalLib/test/test_type_params.py与test_constraintsLib/test/test_type_params.py则从另一角度验证通过annotationlib.call_evaluate_function以VALUE/FORWARDREF/STRING三种格式调用这些求值器结果分别为对象、前向引用包装与字符串说明重命名形参并未影响求值语义。编译器生成链路与内省接口的对接理解了重命名修复后可以把整条链路串起来语法层面type语句与函数/类类型参数由编译器在 Python/codegen.c 的codegen_type_paramsPython/codegen.c与 TypeAlias 分支Python/codegen.c中处理闭包生成codegen_type_param_bound_or_default把 bound/default 表达式放入独立注解作用域编译为小闭包附加默认值(1,)后经codegen_rename_annotations_format_param重命名形参对象构造生成的求值函数通过内部固有函数intrinsic挂到类型对象上。Python/intrinsics.c 中make_typevar_with_bound与make_typevar_with_constraints分别把evaluate_bound、evaluate_constraints传给_Py_make_typevar注册表Python/intrinsics.c中对应INTRINSIC_TYPEVAR_WITH_BOUND、INTRINSIC_TYPEVAR_WITH_CONSTRAINTS、INTRINSIC_SET_TYPEPARAM_DEFAULT等固有操作内省inspect.signature(obj)Lib/inspect.py经Signature.from_callable解析这些闭包对象时__code__.co_localsplusnames中已经是合法的format于是可以正常构造Signature返回(format1, /)。实践提示不要手工调用求值器求值函数是编译器生成的内部对象inspect.signature()现在可以安全地内省它们但业务代码应优先使用annotationlib.call_evaluate_function参见 Lib/annotationlib.py或get_annotations等公开 API避免依赖实现细节默认值语义签名中的format1表示默认以Format.VALUE求值如需字符串或前向引用形式务必显式传入Format.STRING/Format.FORWARDREF回归防护本次修复由 gh-151665 跟踪回归测试位于 Lib/test/test_type_params.py后续若再调整注解作用域的符号命名需确保该测试继续通过文档参照关于type语句与类型参数语法的规范见 Doc/reference/compound_stmts.rstannotationlib模块的公开接口见 Doc/library/annotationlib.rst。小结inspect.signature()无法解析类型别名与类型参数惰性求值器的问题根源于编译器为避开用户名字冲突而在符号表中使用了非法标识符.format作为形参名。修复在 Python/codegen.c 通过符号表阶段用.format、字节码组装后重命名为format的两阶段策略既保护了注解表达式中同名变量的语义又让求值器对外呈现出干净、可内省的签名(format1, /)。这条变更同时是理解 CPython 惰性注解求值机制求值函数生成、Format枚举、call_evaluate_function调用约定的一个极佳切入点。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表