2011-06-15 67 views
14

我有一個方法將(以及其他)字典作爲參數。該方法解析字符串,字典提供了一些子字符串的替換,所以它不必是可變的。在字典參數中使用@ functools.lru_cache

這個函數經常被調用,在冗餘元素上,所以我認爲緩存它會提高效率。

但是,正如您可能已經猜到的一樣,由於dict是可變的,因此無法排除,因此@functools.lru_cache無法修飾我的功能。那麼我該如何克服這個問題呢?

如果只需要標準庫類和方法,則爲加分點。理想情況下,如果它在標準庫中存在某種類型的frozendict,我沒有看到它會使我的一天。

PS:namedtuple只能在最後的手段,因爲它需要一個大的語法轉換。

+0

也許這可以幫助:http://stackoverflow.com/questions/4669391/python-anyone-have-a-memoizing-decorator-that-c​​an-handle-unhashable-論據 – mouad 2011-06-15 13:36:20

+0

我沒有看到這個,但它並沒有真正的幫助。從頭開始寫一個緩存修飾器是不值得在這裏努力,我想堅持標準庫。謝謝你:) – Evpok 2011-06-15 13:40:50

+0

如何子類化'namedtuple'並添加訪問'x [「鍵」]?這可能只是幾行代碼。 – 2011-06-15 13:42:10

回答

7

有關創建哈希的dict類,像這樣什麼:

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

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

工程就像一個魅力,但只有當字典的項目都哈哈,但這是我的情況。然而,你有一些技巧來處理non-hashables'self.items()'嗎? – Evpok 2011-06-15 14:49:17

+1

現在沒有任何方法出現在我的腦海中。你當然可以減少字典,並沿途轉換不可變(字典到frozensets,列表到元組等等)... – mhyfritz 2011-06-15 15:00:22

2

如何繼承namedtuplex["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) 
+0

不錯,但由於'keys'是用戶定義的,它會迫使我定義這個作爲我的調用方法的內部類,我想避免這種情況。好主意,但是,可能有用的時候。 – Evpok 2011-06-15 15:16:46

1

這是一個裝飾者,可以使用像functools.lru_cache。但是,這是在那個只需要一個參數這是一個平映射可哈希值並具有固定的64爲您的使用情況maxsize你將不得不去適應要麼這個例子或您的客戶端代碼的功能針對性。另外,要單獨設置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 

對於一個更通用的方法也可以從第三方庫設置爲key適當的功能使用裝飾@cachetools.cache

1

這是一個使用@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(): 
    ... 
+0

不允許你在裝飾函數上調用'clear_cache()'或'cache_info()'。 – 2017-10-04 17:19:39

0

現在決定放棄我們的用例lru緩存後,我們仍然想出了一個解決方案。這個裝飾器使用json對發送到緩存的args/kwargs進行序列化和反序列化。適用於任何數量的參數。將它用作函數的裝飾器而不是@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