functools.cache() for methods, done correctly.
methodic_cache.cached_method is a decorator that caches the return value of a method, based on the arguments passed to it.
The peculiarity of this library is that it does not store anything on objects themselves, but rather on a separate WeakKeyDictionary where the lifetime of the cache matches the lifetime of the object.
An advantage of this approach over storing the cache on the object itself when needed is that objects will keep their memory footprint smaller thanks to shared key dictionaries. See PEP 412 and The Dictionary Even Mightier - Brandon Rhodes at PyCon 2017, 00:21:02 for more details.
- Simple to use
- Extendable with custom cache backends (e.g. LRUCache, LFUCache, etc.)
- Works with non-hashable objects
- Works with frozen/slotted classes
- Tested for memory leaks
pip install methodic_cachefrommethodic_cacheimportcached_methodclassMyClass:
@cached_methoddefmy_method(self, arg1, arg2):
returnarg1+arg2my_obj=MyClass()
my_obj.my_method(1, 2) # returns 3my_obj.my_method(1, 2) # returns 3 from the cacheClasses that define __slots__ need to have a __weakref__ slot to be able to be weakly referenced:
frommethodic_cacheimportcached_methodclassMyClass:
__slots__= ("my_attr", "__weakref__") # <-- __weakref__ is requireddef__init__(self, my_attr):
self.my_attr=my_attr@cached_methoddefmy_method(self, arg1, arg2):
print(f"Computing {self.my_attr} + {arg1} + {arg2}...")
returnself.my_attr+arg1+arg2my_obj=MyClass(1)
my_obj.my_method(2, 3)
# prints "Computing 1 + 2 + 3..."# returns 6my_obj.my_method(2, 3)
# returns 6You can use any cache backend that implements the MutableMapping interface (e.g. dict, lru_cache, functools.lru_cache, etc.).
The default cache backend is cachetools.Cache(maxsize=math.inf), which will keep the cache bounded to the lifetime of the self object.
You can use a different cache backend by passing it as the cache_factory argument to cached_method:
frommethodic_cacheimportcached_methodfromcachetoolsimportLRUCacheclassMyClass:
@cached_method(cache_factory=lambda: LRUCache(maxsize=1))defmy_method(self, arg1, arg2):
print(f"Computing {arg1} + {arg2}...")
returnarg1+arg2my_obj=MyClass()
my_obj.my_method(1, 1)
# prints Computing 1 + 1...# returns 2my_obj.my_method(1, 1)
# returns 2my_obj.my_method(2, 2)
# prints Computing 2 + 2...# returns 4my_obj.my_method(1, 1) # <-- this will be recomputed because the cache is full# prints Computing 1 + 1...# returns 2