Python 使用带有字典参数的@functools.lru_缓存
我有一个方法,把字典作为一个参数。该方法正在解析字符串,字典提供了一些子字符串的替换,因此它不必是可变的 这个函数经常被调用,而且是在冗余元素上调用的,所以我认为缓存它会提高效率 但是,正如您可能已经猜到的,由于Python 使用带有字典参数的@functools.lru_缓存,python,dictionary,python-3.x,hashable,Python,Dictionary,Python 3.x,Hashable,我有一个方法,把字典作为一个参数。该方法正在解析字符串,字典提供了一些子字符串的替换,因此它不必是可变的 这个函数经常被调用,而且是在冗余元素上调用的,所以我认为缓存它会提高效率 但是,正如您可能已经猜到的,由于dict是可变的,因此不可散列的,@functools.lru\u cache无法修饰我的函数。那么我怎样才能克服这个问题呢 如果它只需要标准的库类和方法,就可以获得额外的积分。理想情况下,如果标准库中存在某种我从未见过的冻结dict,它将成为我的一天 PS:namedtuple只能作为
dict
是可变的,因此不可散列的,@functools.lru\u cache
无法修饰我的函数。那么我怎样才能克服这个问题呢
如果它只需要标准的库类和方法,就可以获得额外的积分。理想情况下,如果标准库中存在某种我从未见过的冻结dict
,它将成为我的一天
PS:
namedtuple
只能作为最后手段,因为它需要一个大的语法转换。创建一个hashabledict
类怎么样:
class HDict(dict):
def __hash__(self):
return hash(frozenset(self.items()))
substs = HDict({'foo': 'bar', 'baz': 'quz'})
cache = {substs: True}
如何子类化
namedtuple
并通过x[“key”]
添加访问权限
这里有一个装饰器,可以像
functools.lru\u cache
一样使用。但这是针对只接受一个参数的函数的,该参数是具有可散列值的平面映射,并且固定的maxsize
为64。对于您的用例,您必须调整这个示例或您的客户机代码。另外,要单独设置maxsize
,必须实现另一个decorator,但我还没有意识到这一点,因为我不需要它
from functools import (_CacheInfo, _lru_cache_wrapper, lru_cache,
partial, update_wrapper)
from typing import Any, Callable, Dict, Hashable
def lru_dict_arg_cache(func: Callable) -> Callable:
def unpacking_func(func: Callable, arg: frozenset) -> Any:
return func(dict(arg))
_unpacking_func = partial(unpacking_func, func)
_cached_unpacking_func = \
_lru_cache_wrapper(_unpacking_func, 64, False, _CacheInfo)
def packing_func(arg: Dict[Hashable, Hashable]) -> Any:
return _cached_unpacking_func(frozenset(arg.items()))
update_wrapper(packing_func, func)
packing_func.cache_info = _cached_unpacking_func.cache_info
return packing_func
@lru_dict_arg_cache
def uppercase_keys(arg: dict) -> dict:
""" Yelling keys. """
return {k.upper(): v for k, v in arg.items()}
assert uppercase_keys.__name__ == 'uppercase_keys'
assert uppercase_keys.__doc__ == ' Yelling keys. '
assert uppercase_keys({'ham': 'spam'}) == {'HAM': 'spam'}
assert uppercase_keys({'ham': 'spam'}) == {'HAM': 'spam'}
cache_info = uppercase_keys.cache_info()
assert cache_info.hits == 1
assert cache_info.misses == 1
assert cache_info.maxsize == 64
assert cache_info.currsize == 1
assert uppercase_keys({'foo': 'bar'}) == {'FOO': 'bar'}
assert uppercase_keys({'foo': 'baz'}) == {'FOO': 'baz'}
cache_info = uppercase_keys.cache_info()
assert cache_info.hits == 1
assert cache_info.misses == 3
assert cache_info.currsize == 3
对于更通用的方法,可以使用第三方库中的decorator,并将适当的函数设置为
键,下面是一个使用@mhyfritz技巧的decorator
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缓存之前
@hash_dict
@functools.lru_cache()
def your_function():
...
在决定暂时删除我们用例的lru缓存之后,我们仍然提出了一个解决方案。此装饰器使用json序列化和反序列化发送到缓存的args/kwargs。可与任意数量的参数一起使用。将其用作函数的装饰器,而不是@lru\u缓存。最大大小设置为1024
def hashable_lru(func):
cache = lru_cache(maxsize=1024)
def deserialise(value):
try:
return json.loads(value)
except Exception:
return value
def func_with_serialized_params(*args, **kwargs):
_args = tuple([deserialise(arg) for arg in args])
_kwargs = {k: deserialise(v) for k, v in kwargs.items()}
return func(*_args, **_kwargs)
cached_function = cache(func_with_serialized_params)
@wraps(func)
def lru_decorator(*args, **kwargs):
_args = tuple([json.dumps(arg, sort_keys=True) if type(arg) in (list, dict) else arg for arg in args])
_kwargs = {k: json.dumps(v, sort_keys=True) if type(v) in (list, dict) else v for k, v in kwargs.items()}
return cached_function(*_args, **_kwargs)
lru_decorator.cache_info = cached_function.cache_info
lru_decorator.cache_clear = cached_function.cache_clear
return lru_decorator
不要使用自定义哈希字典,而是使用它,避免重新发明轮子!这是一本完全可以散列的冻结字典
代码:
然后
@freezeargs
@lru_cache
def func(...):
pass
代码取自@fast_cen的答案
注意:这不适用于递归数据结构;例如,您可能有一个参数是列表,它是不可损坏的。我们邀请您使包装具有递归性,以便它深入到数据结构中,并使每个dict
冻结和每个列表
元组
(我知道OP nolonger想要一个解决方案,但我来这里是为了寻找相同的解决方案,所以将此留给后代)基于,根据建议为深度冻结添加递归:
def deep_freeze(thing):
from collections.abc import Collection, Mapping, Hashable
from frozendict import frozendict
if thing is None or isinstance(thing, str):
return thing
elif isinstance(thing, Mapping):
return frozendict({k: deep_freeze(v) for k, v in thing.items()})
elif isinstance(thing, Collection):
return tuple(deep_freeze(i) for i in thing)
elif not isinstance(thing, Hashable):
raise TypeError(f"unfreezable type: '{type(thing)}'")
else:
return thing
def deep_freeze_args(func):
import functools
@functools.wraps(func)
def wrapped(*args, **kwargs):
return func(*deep_freeze(args), **deep_freeze(kwargs))
return wrapped
也许这能帮上忙:我没见过这个,但它并没有真正的帮助。在这里,从头开始编写缓存装饰器并不值得,我希望坚持使用标准库。无论如何,谢谢你:)子类化namedtuple
并通过x[“key”]
添加访问权限如何?这可能只是几行代码。我知道获取命名元组的唯一方法是调用工厂集合。namedtuple
,它返回类型
,因此,如果我想向命名元组添加\uu getitem\uuuuu
,我必须动态执行,这应该是不可能的,即使它真的很难看。还有其他方法吗?@Evpok:只需对namedtuple()
返回的类型进行子类化:classx(namedtuple(“Y”,“abc”)):…
。工作起来很有魅力,尽管只有当dict的项都是可散列的,但这是我的情况。然而,您是否有一些技巧来处理非哈希表self.items()
?我现在想不出简单的方法。当然,您可以在dict中递归,并在此过程中转换不可变项(dict到冻结集、列表到元组等)……这也很好,但由于键是用户定义的,这将迫使我将其定义为调用方法的内部类,我希望避免这种情况。好主意,但是,可能在某些时候很有用。不允许您在修饰函数上调用clear\u cache()
或cache\u info()
。要提供clear\u cache()
和cache\u info()
调用,只需在返回之前在wrapped
上添加这些函数即可。类似于wrapper.cache\u info=func.cache\u info
和wrapper.cache\u clear=func.cache\u clear
@freezeargs
@lru_cache
def func(...):
pass
def deep_freeze(thing):
from collections.abc import Collection, Mapping, Hashable
from frozendict import frozendict
if thing is None or isinstance(thing, str):
return thing
elif isinstance(thing, Mapping):
return frozendict({k: deep_freeze(v) for k, v in thing.items()})
elif isinstance(thing, Collection):
return tuple(deep_freeze(i) for i in thing)
elif not isinstance(thing, Hashable):
raise TypeError(f"unfreezable type: '{type(thing)}'")
else:
return thing
def deep_freeze_args(func):
import functools
@functools.wraps(func)
def wrapped(*args, **kwargs):
return func(*deep_freeze(args), **deep_freeze(kwargs))
return wrapped