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

MyPy推理问题:装饰器行为无法推断,触发None类型兼容错误

解决装饰器导致的MyPy类型检查错误

先修正代码中的基础逻辑与语法问题,再通过类型标注让MyPy识别装饰器对参数的修改行为:

1. 修复代码中的错误

原代码存在未定义变量、函数调用方式错误、装饰器结构递归问题,先修正这些问题:

import inspect
from functools import wraps
from typing import Optional, Callable, Any, Union, TypeVar

# 定义泛型变量用于标注返回值
R = TypeVar('R')

def default_value(func: Optional[Callable[..., R]] = None, flags: Optional[list[str]] = None) -> Callable[[Callable[..., R]], Callable[..., R]]:
    def decorator(func2: Callable[..., R]) -> Callable[..., R]:
        called_signature = inspect.signature(func2)
        
        @wraps(func2)
        def wrapper(*args: Any, **kwargs: Any) -> R:
            # 绑定参数并应用原函数默认值
            bound_args = called_signature.bind(*args, **kwargs)
            bound_args.apply_defaults()
            
            # 根据flags设置data的默认值
            if flags is None:
                bound_args.arguments["data"] = [0, 0, 0]
            elif "Ones" in flags:
                bound_args.arguments["data"] = [1, 1, 1]
            else:
                bound_args.arguments["data"] = [1, 0, 1]
            
            # 使用修改后的参数调用原函数
            return func2(*bound_args.args, **bound_args.kwargs)
        return wrapper
    
    # 处理装饰器带参数/不带参数的两种调用方式
    if func is not None:
        return decorator(func)
    return decorator

# 示例使用
@default_value(flags=["Ones"])
def testing(data: Optional[Union[list[int], int]] = None, num: int = 3) -> int:
    if isinstance(data, int):
        data = [i for i in range(num)]
    res = len(data) + num
    return res

2. 让MyPy识别装饰器的参数修改行为

MyPy默认无法自动推断装饰器对函数参数的修改,需要通过泛型参数规范明确装饰器的作用,告诉MyPy:经过该装饰器后,data参数不再是Optional类型。

可以借助typing_extensions的工具实现精确类型标注:

import inspect
from functools import wraps
from typing import Optional, Callable, Union, TypeVar
from typing_extensions import ParameterSpecificationVar

# 定义参数规范和返回值类型变量
P = ParameterSpecificationVar('P')
R = TypeVar('R')

def default_value(func: Optional[Callable[P, R]] = None, flags: Optional[list[str]] = None) -> Callable[[Callable[P, R]], Callable[P, R]]:
    def decorator(func2: Callable[P, R]) -> Callable[P, R]:
        called_signature = inspect.signature(func2)
        
        @wraps(func2)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            bound_args = called_signature.bind(*args, **kwargs)
            bound_args.apply_defaults()
            
            # 设置data默认值逻辑
            if flags is None:
                bound_args.arguments["data"] = [0, 0, 0]
            elif "Ones" in flags:
                bound_args.arguments["data"] = [1, 1, 1]
            else:
                bound_args.arguments["data"] = [1, 0, 1]
            
            return func2(*bound_args.args, **bound_args.kwargs)
        return wrapper
    
    if func is not None:
        return decorator(func)
    return decorator

# 修改测试函数的类型标注,明确装饰后data不会为None
@default_value(flags=["Ones"])
def testing(data: Union[list[int], int], num: int = 3) -> int:
    if isinstance(data, int):
        data = [i for i in range(num)]
    res = len(data) + num
    return res

修改后MyPy就能识别到:经过default_value装饰的testing函数,data参数一定会被赋值,不再是Optional类型,len(data)的类型错误会自动消失。

3. 轻量替代方案:手动添加类型断言

如果不想引入typing_extensions,可以在函数内部给data添加类型断言,直接告诉MyPy此时data不为None:

@default_value(flags=["Ones"])
def testing(data: Optional[Union[list[int], int]] = None, num: int = 3) -> int:
    if isinstance(data, int):
        data = [i for i in range(num)]
    # 手动断言data不为None
    assert data is not None
    res = len(data) + num
    return res

这种方式更简单,但需要在每个被装饰的函数中添加断言,不如装饰器类型标注的方式优雅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 09:42:39