Can One Replace or Remove a specific key from functools.lru_cache?
Asked Answered
J

3

10

I'm using a functools.lru_cache to serve temp file paths given certain input*. However in case the path no longer exists, I would like to remove/replace the single corresponding key. The cache_clear() method would be overkill and cache_info() do not appear to help.

Thanks for your help!

* The method being cached streams a fileobj from S3 to a local temp file.

Jaundiced answered 24/7, 2018 at 17:35 Comment(0)
M
7

This was requested via a python.org issue allow to cache_clear(some_key) in lru_cache and rejected. So there is no method in lru_cache that clears a specific entry.

There is an excellent suggestion linked from that issue to implement your own variant using collections.OrderedDict called Foundation for rolling your own LRU cache variants.

Machinate answered 30/8, 2018 at 16:29 Comment(0)
C
3

Use ring.lru to control cache data by their keys

import ring


@ring.lru()
def f(path):
    print('fetching', path)
    return 'some contents in ' + path


if __name__ == '__main__':
    f('path1')
    f('path1')  # no print
    f.delete('path1')
    f('path1')  # print again

There are also set or update to replace.

Charismatic answered 5/5, 2019 at 15:3 Comment(1)
Is this thread safe?Puebla
B
2

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)
Bisectrix answered 13/11, 2020 at 5:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.