Here is my solution to this, adapted from the Foundation for rolling your own LRU cache variants as as suggested by @dmulter.
I added update_wrapper
and overrode __repr__
to make it work more nicely as a wrapper around func
. And then added some methods to remove items from the cache (by passing in the same arguments that would call it), or replace the value in the cache directly (by passing in the value to replace followed by the same arguments you'd use to call the func again). I also added some support for **kwargs
by making a hash from the arguments. I haven't tested it very thoroughly yet, but it seems to work pretty well. Hopefully this is useful to the next person that comes looking for this functionality.
I think this should be able to be used just like @lru_cache
.
from functools import update_wrapper
class MyLRU:
def __init__(self, func, maxsize=128):
self.cache = collections.OrderedDict()
self.func = func
self.maxsize = maxsize
update_wrapper(self, self.func)
def __call__(self, *args, **kwargs):
cache = self.cache
key = self._generate_hash_key(*args, **kwargs)
if key in cache:
cache.move_to_end(key)
return cache[key]
result = self.func(*args, **kwargs)
cache[key] = result
if len(cache) > self.maxsize:
cache.popitem(last=False)
return result
def __repr__(self):
return self.func.__repr__()
def clear_cache(self):
self.cache.clear()
def cache_remove(self, *args, **kwargs):
"""Remove an item from the cache by passing the same args and kwargs"""
key = self._generate_hash_key(*args, **kwargs)
if key in self.cache:
self.cache.pop(key)
def cache_replace(self, value, *args, **kwargs):
key = self._generate_hash_key(*args, **kwargs)
self.cache[key] = value
@staticmethod
def _generate_hash_key(*args, **kwargs):
key = hash(args)+hash(frozenset(sorted(kwargs.items())))
return key
e.g.
@MyLRU
def test(a, b=1, c=2):
print(f'a={a}, b={b}, c={c}')
return (a,b,c)
test(1, 2, 3) # prints and returns
test(1, 2, 3) # only returns (i.e. using cache)
test(1, b=2, c=3) # still prints, but this is a limitation with lru_cache as well
test(1, c=3, b=2) # only returns (an improvement on lru_cache behaviour I think)
test(3, 4, 5) # prints and returns
test.cache_remove(1,2,3)
test(1, 2, 3) # prints again
test(3, 4, 5) # only returns (so the rest of the cache wasn't cleared)
test.cache_replace('hi there', 1, 2, 3)
test(1, 2, 3) # only returns 'hi there' (so cache was replaced)