辞書引数を使用して @functools.lru_cacheを使用します
-
28-10-2019 - |
質問
(とりわけ)辞書を引数として取る方法があります。この方法は文字列を解析することであり、辞書は一部のサブストリングの交換を提供するため、可変である必要はありません。
この関数は非常に頻繁に呼ばれ、冗長な要素で呼ばれるため、キャッシュすると効率が向上すると思いました。
しかし、あなたが推測したように、それ以来 dict
可変であり、したがってハッシュ可能ではありません、 @functools.lru_cache
私の機能を飾ることはできません。では、どうすればこれを克服できますか?
標準のライブラリクラスとメソッドのみが必要な場合はボーナスポイント。理想的には、ある種の存在する場合 frozendict
標準的なライブラリでは、それが私の一日を作るのを見たことがありません。
PS: namedtuple
最後の手段でのみ、大きな構文シフトが必要になるためです。
解決
カスタムハッシュ可能な辞書を使用する代わりに、これを使用して、ホイールの再発明を避けてください!それはすべてハッシュ可能な冷凍辞書です。
https://pypi.org/project/frozendict/
コード:
def freezeargs(func):
"""Transform mutable dictionnary
Into immutable
Useful to be compatible with cache
"""
@functools.wraps(func)
def wrapped(*args, **kwargs):
args = tuple([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
タプル。
(私はOp Nolongerが解決策を望んでいることを知っていますが、私はここに同じ解決策を探しているので、これを将来の世代のために残します)
他のヒント
ハッシュ可能なものを作成するのはどうですか dict
SOのようなクラス:
class HDict(dict):
def __hash__(self):
return hash(frozenset(self.items()))
substs = HDict({'foo': 'bar', 'baz': 'quz'})
cache = {substs: True}
@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():
...
サブクラス化はどうですか namedtuple
アクセスを追加します x["key"]
?
class X(namedtuple("Y", "a b c")):
def __getitem__(self, item):
if isinstance(item, int):
return super(X, self).__getitem__(item)
return getattr(self, item)
これが次のように使用できるデコレーターです functools.lru_cache
. 。ただし、これは機能する関数のみでターゲットにされています 1つの議論 これは フラットマッピング と ハッシュ可能な値 固定されています maxsize
64.ユースケースの場合、この例またはクライアントコードのいずれかを適合させる必要があります。また、を設定します maxsize
個別に、別のデコレーターを実装する必要がありましたが、必要ではないので頭を巻き付けていません。
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
より一般的なアプローチのために、デコレーターを使用できます @cachetools.cache 適切な関数セットを備えたサードパーティライブラリから key
.
今のところ、ユースケースのLRUキャッシュをドロップすることを決定した後、私たちはまだ解決策を思いつきました。このデコレーターは、JSONを使用して、キャッシュに送信されたArgs/Kwargsをシリアル化し、脱直します。任意の数のargsで動作します。 @lru_cacheの代わりに関数のデコレーターとして使用します。最大サイズは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