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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:44:53