使用functools.wraps包装泛型函数时Mypy 1.10报错的问询
带泛型且修改签名的装饰器结合
functools.wraps的mypy报错解决 问题概述
需要实现一个装饰器,要求:
- 修改被包装函数的签名
- 包装后的函数使用泛型类型参数
- 保留原函数的元信息(如名称、文档字符串),通过
functools.wraps实现
但当前代码运行正常,却触发mypy 1.10.0报错,寻求解决方式及对问题性质的判断。
复现代码
from functools import wraps from typing import Callable, TypeVar B = TypeVar('B', bound=str) def str_as_int_wrapper(func: Callable[[int], int]) -> Callable[[B], B]: WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',) WRAPPER_UPDATES = ('__dict__', '__annotations__') @wraps(func, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) def _wrapped_func(val: B) -> B: num = int(val) result = func(num) return val.__class__(result) return _wrapped_func @str_as_int_wrapper def add_one(val: int) -> int: return val + 1
报错信息
test.py:17: error: Incompatible return value type (got "_Wrapped[[int], int, [Never], Never]", expected "Callable[[B], B]") [return-value] test.py:17: note: "_Wrapped[[int], int, [Never], Never].__call__" has type "Callable[[Arg(Never, 'val')], Never]"
注:移除@wraps或将B替换为str时,报错消失。
原因分析
mypy对functools.wraps的类型推断逻辑会绑定原函数的类型签名,即便你手动指定了assigned和updated参数来限制元信息的复制,它仍会认为包装后的函数继承了原函数的int相关类型,而非你定义的泛型B签名,导致返回值类型不匹配。这是mypy在处理泛型装饰器与wraps结合场景时的类型推断限制。
解决方案
方案1:使用typing.cast强制类型转换
在返回包装函数时,用cast明确告诉mypy包装后的函数符合目标泛型类型:
from functools import wraps from typing import Callable, TypeVar, cast B = TypeVar('B', bound=str) def str_as_int_wrapper(func: Callable[[int], int]) -> Callable[[B], B]: WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',) WRAPPER_UPDATES = ('__dict__', '__annotations__') @wraps(func, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) def _wrapped_func(val: B) -> B: num = int(val) result = func(num) return val.__class__(result) return cast(Callable[[B], B], _wrapped_func)
方案2:调整wraps参数,排除__annotations__
报错的核心诱因是updated包含了__annotations__,原函数的int类型注解与泛型B冲突。如果不需要保留原函数的注解,可以从WRAPPER_UPDATES中移除该字段:
from functools import wraps from typing import Callable, TypeVar B = TypeVar('B', bound=str) def str_as_int_wrapper(func: Callable[[int], int]) -> Callable[[B], B]: WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',) # 移除__annotations__避免类型冲突 WRAPPER_UPDATES = ('__dict__',) @wraps(func, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) def _wrapped_func(val: B) -> B: num = int(val) result = func(num) return val.__class__(result) return _wrapped_func
方案3:用typing.Protocol定义泛型调用协议
如果需要更严谨的类型约束,可通过Protocol明确描述包装后函数的调用签名,帮助mypy正确识别泛型类型:
from functools import wraps from typing import Callable, TypeVar, Protocol B = TypeVar('B', bound=str) class StrLikeCallable(Protocol[B]): def __call__(self, val: B) -> B: ... def str_as_int_wrapper(func: Callable[[int], int]) -> StrLikeCallable[B]: WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',) WRAPPER_UPDATES = ('__dict__', '__annotations__') @wraps(func, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) def _wrapped_func(val: B) -> B: num = int(val) result = func(num) return val.__class__(result) return _wrapped_func
关于问题性质与提交报告
这属于mypy当前的类型推断限制,functools.wraps的类型注解未考虑用户手动修改签名并结合泛型的场景。目前mypy的issue仓库中有类似场景的讨论,你可以提交该复现案例到mypy的GitHub仓库,帮助开发团队完善类型推断逻辑。
内容的提问来源于stack exchange,提问作者mgab
相关产品推荐
相关产品推荐

