Предположение
Мне не на 100% ясно, какое именно поведение вы хотите, поэтому я предполагаю, что кэширование должно использоваться только во время выполнения функции, к которой применяется декоратор.
Необходимые биты и кусочки
Вам необходимо
- использовать
functools.lru_cache
для реализации кэширования - создать декоратор, способныйпринять дополнительные аргументы
- использовать
importlib
для импорта класса, указанного в качестве первого аргумента, в декоратор из строки - monkey patch нужного метода с кэшированной версией
Собираем все вместе
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))
Результат
Как видно из вывода, вызов perform_patched_uncached_addition
будет выводить Computing 1 + 2
только один раз, после чего кэшированный результат будетused:
Computing 1 + 2
Patched call nr. 1: 3
Patched call nr. 2: 3
Patched call nr. 3: 3
Любые вызовы, сделанные в класс вне этой функции, будут использовать непатчированную не кэшированную версию метода:
Computing 1 + 2
Unpatched call nr. 1: 3
Computing 1 + 2
Unpatched call nr. 2: 3
Computing 1 + 2
Unpatched call nr. 3: 3
Предостережения
Вам обязательно нужно обратить внимание, если вы планируетеo использовать этот подход в многопоточной среде.Вы не сможете сказать, в какое время будет применено исправление кеша.
Кроме того, кеширование выполняется на основе аргументов, передаваемых методу, который включает self
.Это означает, что каждый экземпляр класса будет иметь свой собственный «кэш».
Примечание
В отличие от кода выше functools.lru_cache
используется в качестве декоратора в большинстве случаев:
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