mirror of https://github.com/mahmoud/boltons.git
initial version of the cacheutils cached decorator
This commit is contained in:
parent
e7bc040a15
commit
219c109242
|
@ -52,8 +52,10 @@ except:
|
||||||
try:
|
try:
|
||||||
from compat import make_sentinel
|
from compat import make_sentinel
|
||||||
_MISSING = make_sentinel(var_name='_MISSING')
|
_MISSING = make_sentinel(var_name='_MISSING')
|
||||||
|
_KWARG_MARK = make_sentinel(var_name='_KWARG_MARK')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_MISSING = object()
|
_MISSING = object()
|
||||||
|
_KWARG_MARK = object()
|
||||||
|
|
||||||
|
|
||||||
PREV, NEXT, KEY, VALUE = range(4) # names for the link fields
|
PREV, NEXT, KEY, VALUE = range(4) # names for the link fields
|
||||||
|
@ -322,6 +324,77 @@ class LRI(dict):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
### Cached decorator
|
||||||
|
# Key-making technique adapted from Python 3.4's functools
|
||||||
|
|
||||||
|
class _HashedKey(list):
|
||||||
|
"""The _HashedKey guarantees that hash() will be called no more than once
|
||||||
|
per cached function invocation.
|
||||||
|
"""
|
||||||
|
__slots__ = 'hash_value'
|
||||||
|
|
||||||
|
def __init__(self, key):
|
||||||
|
self[:] = key
|
||||||
|
self.hash_value = hash(tuple(key))
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.hash_value
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cache_key(args, kwargs, typed=False, kwarg_mark=_KWARG_MARK,
|
||||||
|
fasttypes=frozenset(int, str, frozenset, type(None))):
|
||||||
|
"""Make a cache key from optionally typed positional and keyword
|
||||||
|
arguments. If *typed* is ``True``, ``3`` and ``3.0`` will be
|
||||||
|
treated as separate keys.
|
||||||
|
|
||||||
|
The key is constructed in a way that is flat as possible rather than
|
||||||
|
as a nested structure that would take more memory.
|
||||||
|
|
||||||
|
If there is only a single argument and its data type is known to cache
|
||||||
|
its hash value, then that argument is returned without a wrapper. This
|
||||||
|
saves space and improves lookup speed.
|
||||||
|
"""
|
||||||
|
key = list(args)
|
||||||
|
if kwargs:
|
||||||
|
sorted_items = sorted(kwargs.items())
|
||||||
|
key.append(kwarg_mark)
|
||||||
|
key.extend(sorted_items)
|
||||||
|
if typed:
|
||||||
|
key.extend([type(v) for v in args])
|
||||||
|
if kwargs:
|
||||||
|
key.extend([type(v) for k, v in sorted_items])
|
||||||
|
elif len(key) == 1 and type(key[0]) in fasttypes:
|
||||||
|
return key[0]
|
||||||
|
return _HashedKey(key)
|
||||||
|
|
||||||
|
|
||||||
|
class CachedFunction(object):
|
||||||
|
def __init__(self, func, cache, typed=False):
|
||||||
|
self.func = func
|
||||||
|
self.cache = cache
|
||||||
|
self.typed = typed
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
key = _make_cache_key(args, kwargs, typed=self.typed)
|
||||||
|
try:
|
||||||
|
ret = self.cache[key]
|
||||||
|
except KeyError:
|
||||||
|
ret = self.cache[key] = self.func(*args, **kwargs)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
cn = self.__class__.__name__
|
||||||
|
if self.typed:
|
||||||
|
return "%s(func=%r, typed=%r)" % (cn, self.func, self.typed)
|
||||||
|
return "%s(func=%r)" % (cn, self.func)
|
||||||
|
|
||||||
|
|
||||||
|
def cached(cache, typed=False):
|
||||||
|
def cached_func_decorator(func):
|
||||||
|
return CachedFunction(func, cache, typed=typed)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
def _test_lri():
|
def _test_lri():
|
||||||
import string
|
import string
|
||||||
|
|
Loading…
Reference in New Issue