inspect.signature() 是获取函数形参结构的唯一可靠入口;它返回包含参数名、类型注解、默认值等元信息的 Signature 对象,支持 args/*kwargs 和装饰器场景,但需注意缓存、内置函数兼容性及 Parameter.empty 判断。

inspect.signature() 是获取函数形参结构的唯一可靠入口
Python 3.5+ 中,inspect.signature() 是动态检查函数形参结构的事实标准。它返回 Signature 对象,包含全部参数名、类型注解、默认值、是否可变参数等元信息。别用已弃用的 inspect.getargspec() 或手动解析 __code__.co_varnames —— 它们不处理类型注解、不支持 *args/**kwargs 的精确位置、在某些装饰器下直接失效。
实操建议:
-
inspect.signature(func)必须传入**未被装饰器完全替换**的原函数;若用了@functools.wraps修饰的装饰器,通常可用;若用了@decorator库或自定义闭包且没传递__signature__,会返回错误的签名 - 对内置函数(如
len、print)调用会抛ValueError: no signature found,需提前用inspect.isfunction()或hasattr(func, '__signature__')防御 - 若函数有
__signature__属性(比如被inspect.Signature.replace()显式设置过),inspect.signature()会优先返回它,而非从代码对象推导
遍历 Signature.parameters 理解每个参数的完整属性
Signature.parameters 是一个 OrderedDict,键为参数名,值为 Parameter 实例。每个 Parameter 包含 name、kind(如 Parameter.POSITIONAL_ONLY)、default(Parameter.empty 表示无默认值)、annotation(Parameter.empty 表示无注解)等字段。
常见错误现象:直接打印 param.default 导致输出 <class></class>,误以为是 None —— 实际上必须用 param.default is Parameter.empty 判断是否有默认值。
立即学习“Python免费学习笔记(深入)”;
使用场景举例(检查是否含关键字参数):
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
sig = inspect.signature(my_func) has_kwargs = any(p.kind == p.VAR_KEYWORD for p in sig.parameters.values())
注意:p.VAR_KEYWORD 对应 **kwargs,p.VAR_POSITIONAL 对应 *args,p.KEYWORD_ONLY 是仅限关键字参数(出现在 *args 后的参数)。
处理被装饰器遮蔽的签名:手动补全 __signature__
很多装饰器(尤其是不基于 @functools.wraps 的)会切断原始签名,导致 inspect.signature() 返回空或错误结果。此时不能硬“猜”,而应显式恢复。
实操建议:
- 若装饰器控制权在你手上,在包装函数后手动赋值:
wrapped.__signature__ = inspect.signature(original_func) - 若无法修改装饰器(如第三方库),可用
inspect.unwrap(func)尝试剥离装饰器层,再对其结果调用inspect.signature();但该函数不保证成功,尤其对多层装饰或非标准包装 - 绝对不要依赖
func.__code__.co_argcount推算参数个数——它不区分*args、**kwargs和 keyword-only 参数,也不反映默认值逻辑
兼容性与性能注意点:避免在热路径反复调用 inspect.signature()
inspect.signature() 内部涉及帧对象访问和符号表解析,不是零开销操作。在高频调用场景(如 Web 请求中间件、序列化钩子)中反复执行会明显拖慢性能。
实操建议:
- 对固定函数,签名是不可变的,应缓存结果:
_sig_cache[func] = inspect.signature(func),后续直接复用 - Python 3.9+ 可用
functools.cached_property或简单模块级字典缓存;注意缓存键要用id(func)或func.__qualname__ + str(func.__code__)避免哈希冲突 - 在 PyPy 下,
inspect.signature()开销比 CPython 更高,缓存收益更显著
最易被忽略的是:即使函数没被装饰,inspect.signature() 在首次调用时仍会做一次解析并缓存到函数对象内部(CPython 实现),但这个内部缓存不跨进程、不跨解释器,且某些极端情况(如 monkey patch)可能失效 —— 所以业务代码里自己加一层确定性缓存仍是稳妥做法。

















