import threading
import functools
import time
class link_dict(dict):
__slots__ = ['_pre', '_nxt']
def __init__(self):
self._pre = {None: None}
self._nxt = {None: None}
def __setitem__(self, k, v) -> None:
if k == None:
raise ValueError('Key cannot be None.')
super().__setitem__(k, v)
self._add_link(k, None)
def __delitem__(self, k) -> None:
if k == None:
raise ValueError('Key cannot be None.')
super().__delitem__(k)
self._del_link(k)
def _del_link(self, k):
pre, nxt = self._pre[k], self._nxt[k]
del self._pre[k], self._nxt[k]
self._nxt[pre] = nxt
self._pre[nxt] = pre
def _add_link(self, k, pos):
if k in self._pre:
self._del_link(k)
pre, nxt = self._pre[pos], pos
self._pre[nxt] = self._nxt[pre] = k
self._pre[k] = pre
self._nxt[k] = nxt
@property
def head(self):
return self._nxt[None]
@property
def tail(self):
return self._pre[None]
def __iter__(self):
k = self.head
while k != None:
yield self[k]
k = self._nxt[k]
def __reversed__(self):
k = self.tail
while k != None:
yield self[k]
k = self._pre[k]
def make_key(
args: tuple,
kwds: dict,
):
key = args
if kwds:
key += ('#kwd_mark#', )
for item in kwds.items():
key += item
elif len(key) == 1 and type(key[0]) in {int, str}:
return key[0]
class _HashedSeq(list):
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
return _HashedSeq(key)
def stamped_cache(*, duration=0, maxsize=128, get_key=make_key):
if duration <= 0:
return lambda func: functools.lru_cache(maxsize=maxsize)(func)
last = link_dict()
lock = threading.RLock()
def _cache_wrapper(func):
@functools.lru_cache(maxsize=maxsize)
def func_with_stamp(*args, **kwargs):
return func(*args[:-1], **kwargs)
@functools.wraps(func)
def _func(*args, **kwargs):
nonlocal last, lock
k = get_key(args, kwargs)
nt = time.time()
with lock:
lt = last.get(k)
if lt == None or nt - lt >= duration:
last[k] = nt
if len(last) > maxsize:
del last[last.head]
args += (last[k], )
return func_with_stamp(*args, **kwargs)
return _func
return _cache_wrapper
注:link_dict
可以用 collections.OrderedDict
替代。
写法二,用OrderedDict
import threading
import functools
import collections
import time
import inspect
def make_key(func, *args, **kwargs):
class _HashedSeq(list):
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
sig = inspect.signature(func)
pos_args, kw_args = (), {}
for p, v in zip(sig.parameters.values(), args):
if p.kind == inspect.Parameter.POSITIONAL_ONLY:
pos_args += (v, )
else:
kw_args[p.name] = v
kw_args.update(kwargs)
key = pos_args[:]
for kv in sorted(kw_args.items()):
key += kv
if len(key) == 1 and type(key[0]) in (str, int):
return key[0], pos_args, kw_args
return _HashedSeq(key), pos_args, kw_args
def stamped_cache(*, duration=0, maxsize=128):
if duration <= 0:
return lambda func: functools.lru_cache(maxsize=maxsize)(func)
last = collections.OrderedDict()
lock = threading.RLock()
def _cache_wrapper(func):
@functools.lru_cache(maxsize=maxsize)
def func_with_stamp(*args, **kwargs):
return func(*args[:-1], **kwargs)
@functools.wraps(func)
def _func(*args, **kwargs):
nonlocal last, lock
k, args, kwargs = make_key(func, *args, **kwargs)
nt = time.time()
with lock:
if k in last:
if nt - last[k] >= duration:
last.move_to_end(k)
last[k] = nt
else:
last[k] = nt
if len(last) > maxsize:
last.popitem(last=False)
first = next(iter(last.items()))
if nt - first[1] >= duration:
# 防止无效缓存一直存在
last.popitem(last=False)
args += (last[k], )
return func_with_stamp(*args, **kwargs)
return _func
return _cache_wrapper