使用@functools.lru_cache与字典参数一起

45 投票
9 回答
27957 浏览
提问于 2025-04-16 19:36

我有一个方法,它需要一个字典作为参数,除了其他参数之外。这个方法会解析字符串,而字典则提供一些子字符串的替换,所以这个字典不需要是可变的。

这个函数调用得很频繁,而且有些元素是重复的,所以我想缓存一下它的结果,这样可以提高效率。

但是,正如你可能猜到的,由于 dict 是可变的,因此不能被哈希,所以 @functools.lru_cache 不能用在我的函数上。那么我该如何解决这个问题呢?

如果只需要使用标准库的类和方法,那就更好了。如果标准库里有我没见过的某种 frozendict,那我会非常开心。

另外,namedtuple 只在最后的情况下考虑,因为那样需要很大的语法改变。

9 个回答

12

我们可以考虑创建一个可以被哈希的 dict 类,像这样:

class HDict(dict):
    def __hash__(self):
        return hash(frozenset(self.items()))

substs = HDict({'foo': 'bar', 'baz': 'quz'})
cache = {substs: True}
22

这里有一个装饰器,使用了@mhyfritz的技巧。

def hash_dict(func):
    """Transform mutable dictionnary
    Into immutable
    Useful to be compatible with cache
    """
    class HDict(dict):
        def __hash__(self):
            return hash(frozenset(self.items()))

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        args = tuple([HDict(arg) if isinstance(arg, dict) else arg for arg in args])
        kwargs = {k: HDict(v) if isinstance(v, dict) else v for k, v in kwargs.items()}
        return func(*args, **kwargs)
    return wrapped

只需把它放在你的lru_cache之前就可以了。

@hash_dict
@functools.lru_cache()
def your_function():
    ...
35

与其自己去做一个可以哈希的字典,不如直接使用这个,省得重复造轮子!这是一个冷冻字典,所有的内容都是可以哈希的。

https://pypi.org/project/frozendict/

代码:

from frozendict import frozendict

def freezeargs(func):
    """Convert a mutable dictionary into immutable.
    Useful to be compatible with cache
    """

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        args = (frozendict(arg) if isinstance(arg, dict) else arg for arg in args)
        kwargs = {k: frozendict(v) if isinstance(v, dict) else v for k, v in kwargs.items()}
        return func(*args, **kwargs)
    return wrapped

然后

@freezeargs
@lru_cache
def func(...):
    pass

这段代码来自@fast_cen的回答

注意:这个方法不适用于递归数据结构;比如,你可能有一个参数是列表,而列表是不能哈希的。你可以尝试让这个包装变得递归,这样它就能深入数据结构,把每个dict变成冷冻的,把每个list变成元组。

(我知道提问者不再需要解决方案,但我来这里是为了找同样的解决方案,所以把这个留给未来的人吧)

撰写回答