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
相关产品推荐
相关产品推荐

