使用冻结装饰器结合functools.cache时的代码检查报错解决咨询
解决pylance对带字典参数的缓存装饰器类型检查报错
要缓存一个接收字符串endpoint和字典payload的HTTP查询方法结果,但字典不可哈希,无法直接使用functools.cache。参考方案实现了递归冻结参数的装饰器后功能正常,但pylance抛出类型不兼容报错:
Argument of type "dict[str, Unknown]" cannot be assigned to parameter "args" of type "Hashable" in function "__call__" "dict[str, Unknown]" is incompatible with protocol "Hashable" "__hash__" is an incompatible type Type "None" cannot be assigned to type "(self: dict[str, Unknown]) -> int"
原测试代码:
from functools import cache, wraps from typing import Any, Collection, Hashable, Mapping from immutabledict import immutabledict def deep_freeze(thing: Any) -> Any: if thing is None or isinstance(thing, str): return thing if isinstance(thing, Mapping): return immutabledict({k: deep_freeze(v) for k, v in thing.items()}) if isinstance(thing, Collection): return tuple(deep_freeze(i) for i in thing) if not isinstance(thing, Hashable): raise TypeError(f"Unfreezable type: '{type(thing)}'") return thing def deep_freeze_args(func): @wraps(func) def wrapped(*args, **kwargs): return func(*deep_freeze(args), **deep_freeze(kwargs)) for cache_func in ["cache_info", "cache_clear", "cache_parameters"]: if hasattr(func, cache_func): setattr(wrapped, cache_func, getattr(func, cache_func)) return wrapped @deep_freeze_args @cache def func_a(data): return data if __name__ == "__main__": ext_data = {"a": "b", "c": 3} func_a(ext_data)
解决步骤
核心是给装饰器和相关函数添加准确的类型注解,让pylance识别参数会被转换为可哈希类型:
- 引入泛型类型工具,确保装饰器正确传递原函数的参数和返回值类型
- 明确
deep_freeze的返回类型为可哈希类型 - 给被装饰函数标注清晰的参数、返回值类型
- 给装饰器添加泛型注解,让类型检查器理解参数转换逻辑
修改后的完整代码
from functools import cache, wraps from typing import Any, Collection, Hashable, Mapping, Callable, ParamSpec, TypeVar from immutabledict import immutabledict # 定义泛型参数,用于传递原函数的参数和返回值类型 P = ParamSpec("P") R = TypeVar("R") def deep_freeze(thing: Any) -> Hashable: # 基础可哈希类型直接返回 if thing is None or isinstance(thing, (str, int, float, bool)): return thing # 映射类型转为immutabledict if isinstance(thing, Mapping): return immutabledict({k: deep_freeze(v) for k, v in thing.items()}) # 集合类型(排除字符串)转为元组 if isinstance(thing, Collection) and not isinstance(thing, str): return tuple(deep_freeze(i) for i in thing) # 检查是否可哈希,否则抛出异常 if not isinstance(thing, Hashable): raise TypeError(f"Unfreezable type: '{type(thing)}'") return thing def deep_freeze_args(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: frozen_args = deep_freeze(args) frozen_kwargs = deep_freeze(kwargs) return func(*frozen_args, **frozen_kwargs) # 复制缓存相关方法,保持原缓存装饰器的功能 for cache_attr in ["cache_info", "cache_clear", "cache_parameters"]: if hasattr(func, cache_attr): setattr(wrapped, cache_attr, getattr(func, cache_attr)) return wrapped @deep_freeze_args @cache def func_a(data: Mapping[str, Any]) -> Mapping[str, Any]: return data if __name__ == "__main__": ext_data = {"a": "b", "c": 3} func_a(ext_data)
关键说明
ParamSpec和TypeVar让装饰器精确传递原函数的类型信息,pylance会识别wrapped函数接受的参数与原函数一致deep_freeze返回Hashable,明确告知类型检查器冻结后的参数符合缓存要求- 给
func_a标注Mapping[str, Any]作为参数类型,既兼容普通dict,也匹配冻结后的immutabledict类型 - 修正
Collection判断逻辑,避免误将字符串转换为元组
内容的提问来源于stack exchange,提问作者Poshi
相关产品推荐
相关产品推荐

