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

使用冻结装饰器结合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 19:55:20