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

带ParamSpec注解的函数链式调用时mypy检查失败的解决方案咨询

问题描述

定义以下带类型注解的包装器函数和测试函数:

from typing import Callable, TypeVar, ParamSpec

T1 = TypeVar('T1')
P1 = ParamSpec('P1')
T2 = TypeVar('T2')
P2 = ParamSpec('P2')


def wrapper1(
        func: Callable[P1, T1],
        *args: P1.args,
        **kwargs: P1.kwargs
) -> T1:
    return func(*args, **kwargs)


def wrapper2(
        func: Callable[P2, T2],
        *args: P2.args,
        **kwargs: P2.kwargs
) -> T2:
    return func(*args, **kwargs)

def func() -> int:
    return 1

尝试直接链式调用 wrapper1(wrapper2, func) 时,mypy 1.0.1 会抛出类型不兼容错误:

src/test.py:29: error: Argument 1 to "wrapper1" has incompatible type "Callable[[Arg(Callable[P2, T2], 'func'), **P2], T2]"; expected "Callable[[Arg(Callable[P2, T2], 'func'), **P2], T2]" [arg-type]
src/test.py:29: error: Argument 2 to "wrapper1" has incompatible type "Callable[[], int]"; expected "[Callable[P2, T2], **P2.args]" [arg-type]

但通过定义中间函数间接调用时,mypy 无报错:

def w2() -> int:
    return wrapper2(func)
wrapper1(w2)

请问是否存在合适的注解方式,无需定义中间函数即可让直接链式调用通过 mypy 检查?当前使用 Python 3.11.2,该问题属于 mypy 已知问题(对应问题编号 #13191、#12278)。


解决方案

以下几种方式可以绕过 mypy 的类型推断限制,无需定义中间函数:

1. 显式指定 wrapper1 的类型参数

调用时直接绑定 wrapper1 的泛型参数类型,明确告知 mypy 参数的匹配关系:

wrapper1[Callable[[Callable[[], int]], int], ()](wrapper2, func)

通过显式指定 P1 和 T1 的具体类型,避免 mypy 在嵌套泛型时出现推断混乱。

2. 调整包装器的类型注解逻辑

修改 wrapper1 的注解,直接约束其接受的函数是仅接收单个可调用对象的类型,替换原有的泛型 ParamSpec 匹配逻辑:

from typing import Callable, TypeVar, ParamSpec

# 重新定义泛型
FuncT = TypeVar('FuncT', bound=Callable)
R = TypeVar('R')

def wrapper1(func: Callable[[FuncT], R], f: FuncT) -> R:
    return func(f)

def wrapper2(func: Callable[P, R2], *args: P.args, **kwargs: P.kwargs) -> R2:
    P = ParamSpec('P')
    R2 = TypeVar('R2')
    return func(*args, **kwargs)

def func() -> int:
    return 1

# 直接链式调用可通过 mypy 检查
wrapper1(wrapper2, func)

这种方式通过更具体的类型约束,消除泛型嵌套带来的推断歧义。

3. 使用类型转换临时绕过

如果不想修改原有代码结构,可通过 cast 辅助 mypy 完成类型推断:

from typing import cast, Callable

wrapper1(cast(Callable[[Callable[[], int]], int], wrapper2), func)

这是临时绕过方案,适合需要保留原有代码结构的场景。


内容的提问来源于stack exchange,提问作者nano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:03:16