如何给装饰后的函数添加默认值并保留函数签名的参数与类型?
可以实现,以下是具体方案
你想要的是一个能给函数参数添加默认值,同时让类型检查工具和IDE识别到更新后参数签名的装饰器。原代码的问题在于装饰器返回的还是原函数的严格签名,所以类型检查会报错,下面是可行的实现方式:
核心思路
要实现这个需求需要解决两个关键点:
- 动态填充默认值:通过
inspect模块获取原函数的参数信息,自动给指定参数补充默认值。 - 更新函数签名:修改函数的签名元数据,让类型检查工具(如mypy、PyCharm)识别到哪些参数现在是可选的(带默认值)。
完整实现代码
import inspect import functools from typing import Callable, ParamSpec, TypeVar P = ParamSpec("P") R = TypeVar("R") def add_defaults(defaults: dict[str, any]) -> Callable[[Callable[P, R]], Callable[..., R]]: def decorator(f: Callable[P, R]) -> Callable[..., R]: # 获取原函数的参数签名 original_sig = inspect.signature(f) params_list = list(original_sig.parameters.values()) # 给指定参数添加默认值,生成新的参数列表 new_params = [] for param in params_list: if param.name in defaults: # 替换参数的默认值 updated_param = param.replace(default=defaults[param.name]) new_params.append(updated_param) else: new_params.append(param) # 创建新的函数签名 new_sig = original_sig.replace(parameters=new_params) @functools.wraps(f) def wrapper(*args, **kwargs): # 绑定参数并自动填充默认值 bound_args = new_sig.bind(*args, **kwargs) bound_args.apply_defaults() # 调用原函数 return f(*bound_args.args, **bound_args.kwargs) # 给wrapper设置新的签名,让IDE和类型检查工具识别 wrapper.__signature__ = new_sig return wrapper return decorator
使用示例
@add_defaults({"b": 0}) def my_func(a: str, b: int): print(f"a: {a}, b: {b}") # 现在可以正常调用,b使用默认值0 my_func("a") # 也可以传入指定值 my_func("hello", 10)
关键细节说明
inspect.signature:用来提取原函数的参数结构,包括参数名、类型、是否有默认值等信息。functools.wraps:保留原函数的元数据(如函数名、文档字符串),避免装饰器掩盖原函数的基本信息。__signature__属性:手动设置这个属性后,IDE和类型检查工具会使用新的签名进行校验,不会再提示参数缺失。bind和apply_defaults:自动处理位置参数和关键字参数的绑定,无需手动拼接参数字典,逻辑更简洁可靠。
类型注解优化(可选)
如果需要更精确的类型提示,可以结合PEP 612的Concatenate和overload来完善装饰器的类型注解,让类型检查工具能更准确识别参数变化:
from typing import overload, Concatenate @overload def add_defaults(defaults: dict[str, any]) -> Callable[[Callable[P, R]], Callable[Concatenate[P.args, P.kwargs], R]]: ... # 后续的decorator实现和之前一致
内容的提问来源于stack exchange,提问作者TomTom
相关产品推荐
相关产品推荐

