Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python LRU Cache Decorator Per Instance

Using the LRU Cache decorator found here: http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/

from lru_cache import lru_cache class Test:     @lru_cache(maxsize=16)     def cached_method(self, x):          return x + 5 

I can create a decorated class method with this but it ends up creating a global cache that applies to all instances of class Test. However, my intent was to create a per instance cache. So if I were to instantiate 3 Tests, I would have 3 LRU caches rather than 1 LRU cache that for all 3 instances.

The only indication I have that this is happening is when calling the cache_info() on the different class instances decorated methods, they all return the same cache statistics (which is extremely unlikely to occur given they are being interacted with very different arguments):

CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128) CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128) CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128) 

Is there a decorator or trick that would allow me to easily cause this decorator to create a cache for each class instance?

like image 227
crzysdrs Avatar asked Feb 18 '13 22:02

crzysdrs


People also ask

How do I use LRU cache in python?

One way to implement an LRU cache in Python is to use a combination of a doubly linked list and a hash map. The head element of the doubly linked list would point to the most recently used entry, and the tail would point to the least recently used entry.

Is Python LRU cache thread safe?

No, it's not actually thread safe.

How does Functools LRU cache work?

LRU Cache. The value in the cache is stored as a list of four items(remember root). The first item is the reference to the previous item, the second item is the reference to the next item, the third item is the key for the particular function call, the fourth item is a result.

How do you cache a function in Python?

To memoize a function in Python, we can use a utility supplied in Python's standard library—the functools. lru_cache decorator. Now, every time you run the decorated function, lru_cache will check for a cached result for the inputs provided. If the result is in the cache, lru_cache will return it.


2 Answers

Assuming you don't want to modify the code (e.g., because you want to be able to just port to 3.3 and use the stdlib functools.lru_cache, or use functools32 out of PyPI instead of copying and pasting a recipe into your code), there's one obvious solution: Create a new decorated instance method with each instance.

class Test:     def cached_method(self, x):          return x + 5     def __init__(self):          self.cached_method = lru_cache(maxsize=16)(self.cached_method) 
like image 200
abarnert Avatar answered Sep 26 '22 10:09

abarnert


How about this: a function decorator that wraps the method with lru_cache the first time it's called on each instance?

def instance_method_lru_cache(*cache_args, **cache_kwargs):     def cache_decorator(func):         @wraps(func)         def cache_factory(self, *args, **kwargs):             print('creating cache')             instance_cache = lru_cache(*cache_args, **cache_kwargs)(func)             instance_cache = instance_cache.__get__(self, self.__class__)             setattr(self, func.__name__, instance_cache)             return instance_cache(*args, **kwargs)         return cache_factory     return cache_decorator 

Use it like this:

class Foo:     @instance_method_lru_cache()     def times_2(self, bar):         return bar * 2  foo1 = Foo() foo2 = Foo()  print(foo1.times_2(2)) # creating cache # 4 foo1.times_2(2) # 4  print(foo2.times_2(2)) # creating cache # 4 foo2.times_2(2) # 4 

Here's a gist on GitHub with some inline documentation.

like image 22
z0r Avatar answered Sep 25 '22 10:09

z0r