如何为要求固定部分参数类型的Callable添加类型提示?
如何实现要求特定前置参数类型的包装器类型提示
我需要编写一个包装器函数,接收目标函数、其位置参数和关键字参数并执行。要求目标函数必须包含类型为int的参数a和类型为str的参数b,其余参数类型不限。包装器的示例代码如下:
def wrapper(custom_function: MyType, a: int, b: str, *args, **kwargs) -> float: print(a+3) print(b) return custom_function(a, b, *args, **kwargs)
期望以下测试用例的类型检查结果:
def f1(a: int, b: str) -> float: ... def f2(a: int, b: str, c: float) -> float: ... def f3(a: str, b: str) -> float: ... def f4(a: int, b: str, *args, **kwargs) -> float: ... wrapper(f1, 1, "a") # 预期:通过 wrapper(f2, 1, "a", 4.6) # 预期:通过 wrapper(f3, 1, "a") # 预期:失败(f3的a是str类型,不符合要求) wrapper(f4, 1, "a", [1, 2, 3]) # 预期:通过
我尝试过两种方案但都不符合需求:
- 使用
typing.Protocol和typing.ParamSpec的写法无效,测试1和测试2都失败:from typing import ParamSpec, Protocol P = ParamSpec("P") class MyType(Protocol): def __call__(self, a: int, b: str, P): ... - 使用
Callable[..., float]的方案无法拦截测试3的错误(类型检查不会失败):from typing import Callable MyType = Callable[..., float]
请问我想要的功能是否可行?
可行的实现方案
可以通过正确结合ParamSpec和Protocol来实现需求。关键是在Protocol的__call__方法中正确展开ParamSpec的剩余参数,同时指定返回值类型为float。
正确的代码实现如下:
from typing import ParamSpec, Protocol, TypeVar # 定义返回值类型变量,固定为float也可,用TypeVar更灵活 R = TypeVar("R", bound=float) P = ParamSpec("P") class MyType(Protocol[P, R]): def __call__(self, a: int, b: str, *args: P.args, **kwargs: P.kwargs) -> R: ... def wrapper(custom_function: MyType[P, float], a: int, b: str, *args: P.args, **kwargs: P.kwargs) -> float: print(a + 3) print(b) return custom_function(a, b, *args, **kwargs)
验证测试用例
- 测试1:
wrapper(f1, 1, "a")→ 类型检查通过,f1的参数符合a:int, b:str的要求,无剩余参数。 - 测试2:
wrapper(f2, 1, "a", 4.6)→ 类型检查通过,f2在a和b之后有一个float类型的参数c,与ParamSpec匹配。 - 测试3:
wrapper(f3, 1, "a")→ 类型检查失败,f3的a参数类型是str,与Protocol中要求的int不匹配。 - 测试4:
wrapper(f4, 1, "a", [1, 2, 3])→ 类型检查通过,f4支持可变位置参数和关键字参数,与ParamSpec兼容。
之前方案的问题说明
- 错误的Protocol写法:你之前的代码中直接把
P放在__call__的参数列表里,这是不正确的用法。ParamSpec需要通过*args: P.args和**kwargs: P.kwargs的形式展开,才能匹配目标函数的剩余参数。 - Callable[..., float]的局限性:这个类型是一个宽泛的“接受任意参数返回float”的函数类型,它不会检查前两个参数的具体类型,所以无法拦截f3这种参数类型不匹配的情况。
内容的提问来源于stack exchange,提问作者Paulo-99
相关产品推荐
相关产品推荐

