如何获取Python函数的真实签名?规避inspect.signature的误导
摘要
inspect.signature可获取函数签名,但该签名可能存在误导(通过为函数的__signature__属性赋值即可发现)。
是否存在Python内置函数(优先内置)可获取函数的真实签名?
背景补充
inspect.Parameter.empty被用作参数可选属性kind和default的取值。若在函数定义中使用它,函数的行为有时会与省略该参数时一致,有时则不同。
请参考以下两个函数:
from inspect import Parameter, signature empty = Parameter.empty def chalk(x=empty, y=2): return True def cheese(x, y=2): return True
它们的签名(包括help中显示的签名)完全相同:
assert signature(chalk) == signature(cheese) assert str(signature(chalk)) == str(signature(cheese)) =='(x, y=2)'
但从参数提示的角度(在功能丰富的IDE中输入函数参数时的提示)来看,二者并不相同。在VsCode中:
chalk的提示为(x: empty = empty, y: int = 2) -> Literal[True],显示x带有默认值。cheese的提示为(x: Any, y: int = 2) -> Literal[True],显示x无默认值。
inspect.signature本质上返回的是函数__signature__属性的值,该值与函数的实际行为完全无关。
VsCode的签名提示信息源自何处?是否存在Python内置函数(优先内置)可获取这种真实签名?
问题解答
不存在Python内置函数直接获取这种所谓的"真实"签名,但可以通过解析函数的底层元数据或AST(抽象语法树)来还原定义层面的参数信息。
VSCode提示的来源
VSCode的Python插件(基于Pylance)并不依赖inspect.signature,而是直接读取函数的__code__属性或解析AST:
- 函数的
__code__属性包含字节码、参数数量、默认值集合等底层元数据。比如chalk.__code__.co_defaults是(empty, 2),而cheese.__code__.co_defaults是(2,),这就是两者定义层面的核心差异。 - Pylance结合这些元数据和类型推断,生成更贴合原始定义的参数提示。
获取"真实"签名的方法
- 解析函数AST
import ast import inspect def get_real_param_info(func): source = inspect.getsource(func) tree = ast.parse(source) func_def = tree.body[0] param_details = [] for param in func_def.args.args: info = {"name": param.arg} if param.default is not None: info["has_default"] = True info["default_expr"] = ast.dump(param.default) else: info["has_default"] = False param_details.append(info) return param_details # 测试 print(get_real_param_info(chalk)) print(get_real_param_info(cheese))
- 读取
__code__属性
def get_raw_signature(func): arg_count = func.__code__.co_argcount default_count = len(func.__code__.co_defaults) if func.__code__.co_defaults else 0 param_names = func.__code__.co_varnames[:arg_count] params = [] for idx, name in enumerate(param_names): if idx >= arg_count - default_count: default_val = func.__code__.co_defaults[idx - (arg_count - default_count)] params.append(f"{name}={default_val}") else: params.append(name) return f"({', '.join(params)})" # 测试 print(get_raw_signature(chalk)) print(get_raw_signature(cheese))
需要注意的是,inspect.signature将Parameter.empty视为"无默认值",是因为它模拟的是Python的参数解析行为——调用chalk()和cheese()都会抛出TypeError,所以签名被判定为一致。但从函数定义的原始信息来看,两者确实存在差异,这就是IDE提示不同的原因。
内容的提问来源于stack exchange,提问作者thorwhalen

