带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

