Python inspect模块动态获取函数签名与参数默认值
Python 内置的 inspect 模块能够让我们在程序运行时“偷看”函数的内部结构,无需手动查阅源代码。这在编写装饰器、API 文档生成工具或动态调用函数时非常有用。
1. 准备一个演示用的目标函数
首先,我们需要一个包含多种参数类型(位置参数、关键字参数、默认值、类型注解)的函数作为分析对象。
编写以下代码并保存:
def process_data(user_id, name="Anonymous", *, role="guest", active=True):
"""
模拟一个数据处理函数。
:param user_id: 用户ID
:param name: 用户名,默认为 Anonymous
:param role: 用户角色,仅限关键字参数
:param active: 是否激活,默认为 True
"""
print(f"Processing {user_id}: {name} ({role}, active={active})")
2. 获取函数的签名对象
inspect.signature 是核心入口,它将函数的可调用信息封装为一个 Signature 对象。
导入 inspect 模块,并调用 signature 方法:
import inspect
# 获取函数签名
sig = inspect.signature(process_data)
# 打印签名概览
print(f"函数签名: {sig}")
# 输出示例: (user_id, name='Anonymous', *, role='guest', active=True)
解析 sig 对象,你会发现它包含了参数列表和返回值注解。
3. 遍历并提取参数详细信息
Signature 对象的 parameters 属性是一个有序映射(类似字典),存储了所有的参数信息。我们可以遍历它来获取每一个参数的详情。
编写如下循环代码来提取参数名、默认值和类型:
print("\n--- 参数详细解析 ---")
for param_name, param_obj in sig.parameters.items():
# 获取参数名称
name = param_obj.name
# 获取参数默认值
# 注意:如果参数没有默认值,其 default 属性是 inspect.Parameter.empty
default = param_obj.default
default_str = repr(default) if default is not inspect.Parameter.empty else "无默认值"
# 获取参数类型注解
annotation = param_obj.annotation
annotation_str = str(annotation) if annotation is not inspect.Parameter.empty else "无注解"
# 获取参数种类(POSITIONAL, KEYWORD_ONLY, VAR_POSITIONAL 等)
kind = param_obj.kind
print(f"参数名: {name}")
print(f" 默认值: {default_str}")
print(f" 类型注解: {annotation_str}")
print(f" 参数种类: {kind}")
print("-" * 20)
4. 理解参数属性对照表
在处理 param_obj(即 Parameter 对象)时,你会频繁接触到以下几个属性。下表列出了它们的含义:
| 属性名 | 类型/返回值 | 说明 |
|---|---|---|
name |
str |
参数的名称字符串。 |
default |
Any |
参数的默认值。重点:如果没有默认值,该值为 inspect.Parameter.empty,而不是 None。 |
annotation |
type |
参数的类型提示(如 int, str)。若无注解,值为 inspect.Parameter.empty。 |
kind |
_ParameterKind |
枚举值,描述参数的模式。常见的有 POSITIONAL_OR_KEYWORD(普通参数)、KEYWORD_ONLY(* 后的参数)、VAR_POSITIONAL(*args)。 |
5. 实战:构建一个参数检查字典
在很多场景下(如构建动态 SQL 或 HTTP 请求),我们需要知道哪些参数是被用户“省略”了的,从而使用默认值。
运行以下代码,生成一个包含“参数名: 默认值”的字典:
def get_defaults_map(func):
"""提取函数的所有有默认值的参数及其默认值"""
sig = inspect.signature(func)
defaults = {}
for name, param in sig.parameters.items():
# 仅保存拥有默认值的参数
if param.default is not inspect.Parameter.empty:
defaults[name] = param.default
return defaults
# 测试提取
defaults = get_defaults_map(process_data)
print("默认值字典:", defaults)
# 预期输出: {'name': 'Anonymous', 'role': 'guest', 'active': True}
注意:user_id 不会出现在这个字典中,因为它没有默认值。
6. 动态绑定参数调用函数
获取签名的最终目的往往是为了安全地调用函数。我们可以利用 Signature.bind 方法,将一个字典动态映射到函数参数上,Python 会自动处理位置参数和关键字参数的匹配。
执行以下代码模拟动态调用:
# 模拟从外部(如配置文件或HTTP请求)获取的参数数据
input_data = {
"user_id": 1001,
"role": "admin"
# 故意不传 name 和 active,测试是否自动填充默认值
}
try:
# 将输入数据绑定到函数签名
bound_args = sig.bind(**input_data)
# 应用默认值
# bind() 方法如果遇到缺失参数会报错,除非该参数有默认值
# 但 bind 默认行为是:提供的参数必须足够填充无默认值的位置参数
# 这里 user_id 已提供,name 虽未提供但在 user_id 之后且为非仅关键字参数?
# 不,name 是普通参数。bind 严格要求参数完整性。
# 更好的做法是:先绑定,再补全默认值
bound_args.apply_defaults()
print(f"\n最终传递的参数: {bound_args.arguments}")
# 使用 *bound_args.args, **bound_args.kwargs 调用函数
process_data(*bound_args.args, **bound_args.kwargs)
except TypeError as e:
print(f"参数错误: {e}")
关键点:
bound_args.apply_defaults() 是 inspect 模块中非常方便的方法。它会自动检查签名,将那些调用方未提供但函数定义了默认值的参数,自动填充到 bound_args.arguments 字典中。
7. 判断参数是否为仅关键字参数
在 process_data 中,role 和 active 位于 * 之后,这意味着它们必须通过关键字传递。我们可以通过检查 param.kind 来识别这一点。
使用枚举比较进行判断:
for name, param in sig.parameters.items():
if param.kind == inspect.Parameter.KEYWORD_ONLY:
print(f"'{name}' 是仅关键字参数,必须使用关键字形式调用。")
| 常用 Kind 常量 | 含义 | 示例定义 |
|---|---|---|
POSITIONAL_OR_KEYWORD |
可以是位置参数或关键字参数 | def f(a) |
VAR_POSITIONAL |
任意数量的位置参数 (*args) |
def f(*args) |
KEYWORD_ONLY |
必须是关键字参数 | def f(*, a) |
VAR_KEYWORD |
任意数量的关键字参数 (**kwargs) |
def f(**kwargs) |

暂无评论,快来抢沙发吧!