In standard Python, you can do this to cache the return value of an expensive function:
@lru_cache() def function(n): ...
This works well, but it does not work very well for class methods:
class MyClass: @lru_cache() def function(self): ...
All instances of MyClass will share the same cache. Often, especially for immutable instances, a per-instance cache of size 1 is desired. Python 3.8 adds a useful cached_property decorator, but that does not provide a cache_clear method like lru_cache does.
So how do you create a per-instance cache for class methods with a clear function? This is what I came up with:
class _CacheCallable: MISSING = object() def __init__(self, cache_key: str, method: types.FunctionType, instance: Any): self.method = method self.instance = instance self.cache = instance.__dict__ self.cache_key = cache_key def __call__(self) -> Any: cached = self.cache.get(self.cache_key, self.MISSING) if cached is self.MISSING: cached = self.method(self.instance) self.cache[self.cache_key] = cached return cached def cache_clear(self): self.cache[self.cache_key] = self.MISSING class cache: def __init__(self, method: types.FunctionType): self.method = method self.cache_key = f"_cache_decorator_{method.__name__}" def __get__(self, instance: Any, _): assert instance, "Method must be called on an object." return _CacheCallable(self.cache_key, self.method, instance)
Note that this code is missing locks for threading (just look at the implementation of cached_property on how to add that). It also does not handle any function arguments (also relatively easy to add).
Usage:
class C: def __init__(self, i): self.i = i @cache def f(self): print(f"C.f() called.") return self.i c1 = C(1) c2 = C(2) print(c1.f(), c2.f()) print(c1.f(), c2.f())
Output:
C.f() called. C.f() called. 1 2 1 2