使用@functools.lru_cache与字典参数一起
我有一个方法,它需要一个字典作为参数,除了其他参数之外。这个方法会解析字符串,而字典则提供一些子字符串的替换,所以这个字典不需要是可变的。
这个函数调用得很频繁,而且有些元素是重复的,所以我想缓存一下它的结果,这样可以提高效率。
但是,正如你可能猜到的,由于 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
变成元组。
(我知道提问者不再需要解决方案,但我来这里是为了找同样的解决方案,所以把这个留给未来的人吧)