inspect.signature() 能获取函数定义时的参数名、类型注解、默认值、参数种类等静态签名信息,但不能获取调用时的实际参数、闭包变量或装饰器动态修改后的签名。

inspect.signature() 能拿到什么,不能拿到什么
inspect.signature() 返回的是 Signature 对象,它包含参数名、类型注解、默认值、是否可变参数(*args / **kwargs)等元信息,但不执行函数、不触发副作用、也不解析运行时传入的实际值。
常见误解是以为它能“看到”调用时的实参——其实不能。它只反映函数定义时的签名结构。
- 能拿到:
Parameter.name、Parameter.default、Parameter.annotation、Parameter.kind(比如POSITIONAL_ONLY) - 拿不到:调用栈里的实际传参、闭包捕获的变量、装饰器动态改写的签名(除非装饰器显式保留)
- 注意:
lambda函数的签名里参数名可能是arg0,arg1(CPython 实现细节),不可依赖
如何安全提取所有参数名和默认值
直接遍历 signature.parameters.values() 是最稳妥的方式,但要注意 Parameter.empty 的判断逻辑,别用 is None 或 == None。
import inspect
<p>def example(a, b: int = 42, *args, c=None, **kwargs):
pass</p><p>sig = inspect.signature(example)
for param in sig.parameters.values():
name = param.name
default = param.default if param.default is not inspect.Parameter.empty else '<no default>'
kind = param.kind
print(f'{name} ({kind.name}): {default}')
</no></p>
-
param.default is inspect.Parameter.empty是唯一可靠的“无默认值”判断方式 -
*args和**kwargs的kind分别是VAR_POSITIONAL和VAR_KEYWORD,它们的default永远是empty - 如果函数被
@functools.wraps包裹但没传assigned参数,signature可能返回被包装函数的签名而非 wrapper 本身的——检查前先确认装饰器是否正确传递了__signature__
处理带类型注解或字符串化注解的函数
Python 3.10+ 支持 from __future__ import annotations,此时注解是字符串;而 inspect.signature() 默认不求值,所以你看到的是原始字符串,不是真正的类型对象。
需要真实类型时,得手动调用 typing.get_type_hints():
import inspect import typing <p>def f(x: 'list[int]', y: str) -> 'dict[str, int]': ...</p><p>sig = inspect.signature(f) param_x = sig.parameters['x'] print(param_x.annotation) # 输出:'list[int]'</p><h1>要转成真实类型,必须:</h1><p>try: hints = typing.get_type_hints(f) print(hints['x']) # <class> except Exception: pass # 注解非法或作用域不可达时会失败 </class></p>
-
get_type_hints()会尝试在函数定义的作用域里eval字符串注解,失败就抛异常,不能假设它总成功 - 如果函数在
exec()或动态模块中定义,且缺少globals/locals上下文,get_type_hints()很可能失败 - 注解里用了未导入的名称(比如写了
'DataFrame'但没 import pandas)也会崩
为什么 inspect.signature 有时返回的是 BoundArguments 而不是 Signature
它不会。这是个常见混淆点:inspect.signature() 永远返回 Signature;而 sig.bind() 或 sig.bind_partial() 才返回 BoundArguments。
如果你看到类似 missing 1 required argument 的错误,大概率是误用了 bind() 并传了不全的参数,而不是 signature() 本身的问题。
-
sig.bind(a=1)→ 报错:缺少b;sig.bind(a=1, b=2)→ 成功返回BoundArguments -
BoundArguments.arguments是个 dict,键是参数名,值是绑定后的实参——这才是接近“运行时参数快照”的东西,但它仍不等于调用发生时的真实状态(比如没考虑nonlocal修改) - 想做参数校验或日志记录,
bind()+apply_defaults是常用组合,但注意它不处理__defaults__以外的动态默认逻辑(比如函数体内写的b = b or [])
真正难的不是取签名,而是判断这个签名是否还“有效”——比如被装饰器重写、在不同 Python 版本间行为不一致、或注解跨模块引用失效。这些没法靠一行 inspect.signature() 解决,得结合上下文做防御性检查。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











