You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为要求固定部分参数类型的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兼容。

之前方案的问题说明
  1. 错误的Protocol写法:你之前的代码中直接把P放在__call__的参数列表里,这是不正确的用法。ParamSpec需要通过*args: P.args和**kwargs: P.kwargs的形式展开,才能匹配目标函数的剩余参数。
  2. Callable[..., float]的局限性:这个类型是一个宽泛的“接受任意参数返回float”的函数类型,它不会检查前两个参数的具体类型,所以无法拦截f3这种参数类型不匹配的情况。

内容的提问来源于stack exchange,提问作者Paulo-99

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 03:33:33