如何验证每组参数都匹配函数签名的多组参数序列类型?
批量函数测试的类型提示解决方案
问题背景
实现了一个批量执行目标函数并输出耗时的工具函数,但在给args和kwargs参数加类型提示时遇到问题:直接使用ParamSpec的P.args和P.kwargs作为列表元素类型会报错,因为这两个成员仅能配合*args/**kwargs参数使用。需要找到一种方式,让类型检查器能验证多组参数序列中的每一组都匹配目标函数的签名。
原错误代码:
from time import monotonic from itertools import chain from datetime import timedelta from typing import Callable, Sequence, Any, ParamSpec P = ParamSpec('P') def test_function( func: Callable[P, Any], args: list[P.args], # 错误:ParamSpec的"args"成员仅在配合*args参数时有效 kwargs: list[P.kwargs] # 错误:ParamSpec的"kwargs"成员仅在配合**kwargs参数时有效 ) -> None: for a, kw in zip(args, kwargs): args_str = ', '.join(chain( (str(i) for i in a), (f"{k}={v}" for k, v in kw.items()) )) start = monotonic() func(*a, **kw) print( f"{func.__name__}({args_str}) " f"executed in: {timedelta(seconds=monotonic() - start)}" )
解决方案
利用ParamSpec的特性,将P.args和P.kwargs作为序列的元素类型(而非直接作为列表类型的参数),类型检查器即可正确验证每组参数是否匹配目标函数签名。有两种实现方式:
方式1:保留原参数结构
将args和kwargs的类型改为Sequence[P.args]和Sequence[P.kwargs],同时增加长度一致性检查避免zip截断参数对:
from time import monotonic from itertools import chain from datetime import timedelta from typing import Callable, Sequence, Any, ParamSpec P = ParamSpec('P') def test_function( func: Callable[P, Any], args: Sequence[P.args], kwargs: Sequence[P.kwargs] ) -> None: if len(args) != len(kwargs): raise ValueError("args和kwargs序列长度必须一致") for a, kw in zip(args, kwargs): args_str = ', '.join(chain( (str(i) for i in a), (f"{k}={v}" for k, v in kw.items()) )) start = monotonic() func(*a, **kw) print( f"{func.__name__}({args_str}) " f"executed in: {timedelta(seconds=monotonic() - start)}" )
方式2:合并参数对为单个序列
将每组位置参数和关键字参数打包成元组,作为单个序列的元素,更直观且避免长度不匹配问题:
from time import monotonic from itertools import chain from datetime import timedelta from typing import Callable, Sequence, Any, ParamSpec, Tuple P = ParamSpec('P') def test_function( func: Callable[P, Any], param_pairs: Sequence[Tuple[P.args, P.kwargs]] ) -> None: for a, kw in param_pairs: args_str = ', '.join(chain( (str(i) for i in a), (f"{k}={v}" for k, v in kw.items()) )) start = monotonic() func(*a, **kw) print( f"{func.__name__}({args_str}) " f"executed in: {timedelta(seconds=monotonic() - start)}" )
用法示例
方式1调用
def f1(a: int, b: float, *, c: int): return a + b + c # 类型检查通过,正常执行 test_function(f1, [(1, 2), (3, 4)], [{'c': -1}, {'c': -2}])
方式2调用
def f1(a: int, b: float, *, c: int): return a + b + c # 类型检查通过,正常执行 test_function(f1, [((1, 2), {'c': -1}), ((3, 4), {'c': -2})])
效果说明
使用上述定义后,mypy、pyright等类型检查器会自动验证:
- 位置参数元组的类型、数量是否匹配目标函数的位置参数
- 关键字参数字典的键名、值类型是否匹配目标函数的关键字参数
- 若参数不匹配,会在静态检查阶段抛出错误,提前发现问题
内容的提问来源于stack exchange,提问作者Олексій Холостенко
相关产品推荐
相关产品推荐

