使用Callable标注函数时如何保留关键字参数并修复类型错误?
如何修复带
stringify选项的装饰器的mypy类型错误 问题背景
我需要给多个函数添加stringify选项:当stringify=True时,函数返回str(result)而非原本的result。为避免重复实现逻辑,我使用了装饰器,但代码运行正常的情况下,mypy报出类型错误,同时丢失关键字参数自动补全:
Unexpected keyword argument "value" for "func1"mypy(error)Unexpected keyword argument "stringify" for "func1"mypy(error)
我的简化代码如下:
from typing import Callable, Union def WithStringifyOption(func: Callable[[int], int]) -> Callable[[int, bool], Union[int, str]]: def wrapper(value: int, stringify: bool = False) -> Union[int, str]: result = func(value) return str(result) if stringify else result return wrapper # 原函数 def func0(value: int) -> int: return value * 2 # 装饰后的函数 @WithStringifyOption def func1(value: int) -> int: return value * 2 func0(value=2) func1(value=3, stringify=True)
根据typing.Callable官方文档:
没有语法用于标注可选参数或关键字参数;这类函数类型很少用作回调类型。
注:这是实际项目的简化示例,我需要为每个函数添加更复杂的逻辑,因此无法重构所有函数重复实现该逻辑,也不想大量添加type: ignore,更不想替换关键字参数为位置参数。
解决方案
使用Python 3.10+引入的typing.ParamSpec和typing.TypeVar来精确标注装饰器的参数和返回值类型,让mypy能正确识别关键字参数和可选参数。
修改后的代码
from typing import Callable, ParamSpec, TypeVar, Union # 定义参数规格和返回值类型变量 P = ParamSpec("P") R = TypeVar("R") def WithStringifyOption(func: Callable[P, R]) -> Callable[P + (bool,), Union[R, str]]: def wrapper(*args: P.args, stringify: bool = False, **kwargs: P.kwargs) -> Union[R, str]: result = func(*args, **kwargs) return str(result) if stringify else result return wrapper # 原函数 def func0(value: int) -> int: return value * 2 # 装饰后的函数 @WithStringifyOption def func1(value: int) -> int: return value * 2 # 测试调用,mypy不再报错,关键字参数补全正常 func0(value=2) func1(value=3, stringify=True) func1(3, stringify=True) # 位置参数也支持
关键说明
ParamSpec("P"):捕获原函数所有参数(包括位置参数、关键字参数)的类型信息。TypeVar("R"):捕获原函数的返回值类型。Callable[P + (bool,), Union[R, str]]:表示装饰后的函数参数是原函数参数加上可选的stringify布尔参数,返回值为原返回值或字符串类型。- 装饰器内部的
wrapper用*args: P.args和**kwargs: P.kwargs接收原函数的所有参数,确保关键字参数能被正确传递和识别。
如果需要支持Python 3.8+版本,可以使用typing_extensions库中的ParamSpec和TypeVar,用法完全一致。
内容的提问来源于stack exchange,提问作者Vince M
相关产品推荐
相关产品推荐

