У меня есть функция, которая возвращает список, скажем, list_x.
def result(val):
..
return(list_x)
Я вызываю result() каждую минуту и сохраняю список.
def other_func():
#called every minute
new_list = result(val)
Я хотел бы хранить значение new_list в течение часа (в каком-то кеше в памяти может быть?), А затем обновлять его снова, в основном вызывать результаты() через час, а не каждую минуту. Я читал о functools.lru_cache но это не поможет здесь, я думаю. Есть идеи?
Создание одноэлементного кеша с временем жизни довольно тривиально:
_last_result_time = None
_last_result_value = None
def result(val):
global _last_result_time
global _last_result_value
now = datetime.datetime.now()
if not _last_result_time or now - _last_result_time > datetime.timedelta(hours=1):
_last_result_value = <expensive computation here>
_last_result_time = now
return _last_result_value
Если вы хотите обобщить это как декоратор, это не намного сложнее:
def cache(ttl=datetime.timedelta(hours=1)):
def wrap(func):
time, value = None, None
@functools.wraps(func)
def wrapped(*args, **kw):
nonlocal time
nonlocal value
now = datetime.datetime.now()
if not time or now - time > ttl:
value = func(*args, **kw)
time = now
return value
return wrapped
return wrap
Если вы хотите, чтобы он обрабатывал разные аргументы, сохраняя время для жизни для каждого из них:
def cache(ttl=datetime.timedelta(hours=1)):
def wrap(func):
cache = {}
@functools.wraps(func)
def wrapped(*args, **kw):
now = datetime.datetime.now()
# see lru_cache for fancier alternatives
key = tuple(args), frozenset(kw.items())
if key not in cache or now - cache[key][0] > ttl:
value = func(*args, **kw)
cache[key] = (now, value)
return cache[key][1]
return wrapped
return wrap
Разумеется, вы можете добавлять к нему дополнительные функции - придать максимальный размер и выселить по времени хранения или LRU или что-то еще, что вам нужно, выставить статистику кеша в качестве атрибутов на украшенной функции и т.д. Реализация lru_cache
в stdlib должна помогите показать вам, как делать большую часть сложных вещей (так как почти все они).
Обычно декоратор прекрасно это понимает
def cache(fn=None,time_to_live=3600*24): # one DAY default (or whatever)
if not fn: return functools.partial(cache,time_to_live=time_to_live)
my_cache = {}
def _inner_fn(*args,**kwargs)
kws = sorted(kwargs.items()) # in python3.6+ you dont need sorted
key = tuple(args)+tuple(kw)
if key not in my_cache or time.time() > my_cache[key]['expires']:
my_cache[key] = {"value":fn(*args,**kwargs),"expires":time.time()+ time_to_live}
return my_cache[key]
return __inner_fn
@cache(time_to_live=3600) # an hour
def my_sqrt(x):
return x**0.5
@cache(time_to_live=60*30) # 30 mins
def get_new_emails():
return my_stmp.get_email_count()
поскольку в стороне это встроено в memcache, и это может быть лучшим решением (я не уверен, в какой проблемной области вы работаете)
вы также можете использовать вложенные функции
def cache(time_to_live=3600*24): # one DAY default (or whatever)
def _wrap(fn):
my_cache = {}
def _inner_fn(*args,**kwargs)
kws = sorted(kwargs.items()) # in python3.6+ you dont need sorted
key = tuple(args)+tuple(kw)
if key not in my_cache or time.time() > my_cache[key]['expires']:
my_cache[key] = {"value":fn(*args,**kwargs),"expires":time.time()+ time_to_live}
return my_cache[key]
return _inner_fn
return _wrap
partial
является умной, но IMO более уродливой, чем просто наличие другого уровня замыкания. Теперь вы разрешаете вызывать cache
двумя разными способами: cache(time_to_live=___)(function)
или cache(function, time_to_live=___)
. Просто вопрос вкуса, и мне нравится решение.
@cache
или @cache(time_to_live=1000)
... но да, у вас есть хорошая точка зрения ... мне не нравятся вложенные функции ... и вообще, мне нравится это как решение, поэтому я всегда использую это при приеме kwargs для декоратора (я НЕ @cache(1000)
и хочу, чтобы это не разрешалось)
Создайте функцию, которая действует как кеш, назовем ее result_cacher
.
import time
lastResultCache = 0
resultCache = None
def result_cacher():
if time.time() - lastResultCache >= 3600: #Checks if 3600 sec (1 hour) has passed since the last cache
lastResultCache = time.time()
resultCache = result()
return resultCache
Эта функция проверяет, прошел ли час, обновляет кеш, если он есть, а затем возвращает кеш.
Если вы хотите применить кеширование для каждого отдельного входа, а не для всякий раз, когда вызывается функция, используйте словари для lastResultCache
и resultCache
.
import time
lastResultCache = {}
resultCache = {}
def result_cacher(val):
#.get() gets value for key from dict, but if the key is not in the dict, it returns 0
if time.time() - lastResultCache.get(val, 0) >= 3600: #Checks if 3600 sec (1 hour) has passed since the last cache
lastResultCache[val] = time.time()
resultCache[val] = result(val)
return resultCache.get(val)
Решение с использованием ring
@ring.lru(expire=60*60) # seconds
def cached_function(keys):
return ...
Если вам не нужна политика LRU,
@ring.dict(expire=60*60) # seconds
def cached_function(keys):
return ...
Декоратор ttl_cache
в cachetools==3.1.0
работает во многом как functools.lru_cache
, но со временем жизни.
import cachetools.func
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
def example_function(key):
return get_expensively_computed_value(key)
class ExampleClass:
EXP = 2
@classmethod
@cachetools.func.ttl_cache()
def example_classmethod(cls, i):
return i* cls.EXP
@staticmethod
@cachetools.func.ttl_cache()
def example_staticmethod(i):
return i * 3
__call__
, чтобы способ сохранения состояния кэша скрывался внутри переменных-членов объекта а не глобальные.