Assumption
It is not 100% clear to me what exact behavior you want, so I will assume that the caching shall only be used during execution of the function to which the decorator is applied.
Necessary bits and pieces
You will need to
- use
functools.lru_cache
to realize the caching
- create a decorator that is able to take additional arguments
- use
importlib
to import the class given as first argument to the decorator from string
- monkey patch the desired method with a cached version
Putting it together
import importlib
from functools import lru_cache
class Addition:
def __init__(self, a):
self.a = a
def uncached_addition(self, b):
# print is only used to demonstrate if the method is actually called or not
print(f"Computing {self.a} + {b}")
return self.a + b
class cache_patch:
def __init__(self, method_as_str, ttl):
# split the given path into module, class and method name
class_as_str, method_name = method_as_str.rsplit(".", 1)
module_path, class_name = class_as_str.rsplit(".", 1)
self.clazz = getattr(importlib.import_module(module_path), class_name)
self.method_name = method_name
self.ttl = ttl
def __call__(self, func):
def wrapped(*args, **kwargs):
# monkey patch the original method with a cached version
uncached_method = getattr(self.clazz, self.method_name)
cached_method = lru_cache(maxsize=self.ttl)(uncached_method)
setattr(self.clazz, self.method_name, cached_method)
result = func(*args, **kwargs)
# replace cached method with original
setattr(self.clazz, self.method_name, uncached_method)
return result
return wrapped
@cache_patch('__main__.Addition.uncached_addition', ttl=128)
def perform_patched_uncached_addition(a, b):
d = Addition(a=1)
print("Patched nr. 1\t", d.uncached_addition(2))
print("Patched nr. 2\t", d.uncached_addition(2))
print("Patched nr. 3\t", d.uncached_addition(2))
print()
if __name__ == '__main__':
perform_patched_uncached_addition(1, 2)
d = Addition(a=1)
print("Unpatched nr. 1\t", d.uncached_addition(2))
print("Unpatched nr. 2\t", d.uncached_addition(2))
print("Unpatched nr. 3\t", d.uncached_addition(2))
Result
As you can see from the output, calling perform_patched_uncached_addition
will only output Computing 1 + 2
once, afterwards the cached result is used:
Computing 1 + 2
Patched call nr. 1: 3
Patched call nr. 2: 3
Patched call nr. 3: 3
Any calls made to the class outside of this function will use the unpatched, non cached version of the method:
Computing 1 + 2
Unpatched call nr. 1: 3
Computing 1 + 2
Unpatched call nr. 2: 3
Computing 1 + 2
Unpatched call nr. 3: 3
Caveats
You certainly need to pay attention if you plan to use this approach in a multithreaded environment. You won't be able to tell at what times the cache patch will be applied.
Also, the caching is done based on the arguments passed to the method, which includes self
. This means that every instance of the class will have their own "cache".
Side note
Unlike in the code above functools.lru_cache
is used as a decorator in most cases:
class Addition:
def __init__(self, a):
self.a = a
@lru_cache(maxsize=128)
def cached_addition(self, b):
print(f"Computing {self.a} + b")
return self.a + b