Кэширование в памяти с ограничением по времени в python

1

У меня есть функция, которая возвращает список, скажем, list_x.

def result(val):
    ..
    return(list_x)

Я вызываю result() каждую минуту и сохраняю список.

def other_func():
    #called every minute
    new_list = result(val)

Я хотел бы хранить значение new_list в течение часа (в каком-то кеше в памяти может быть?), А затем обновлять его снова, в основном вызывать результаты() через час, а не каждую минуту. Я читал о functools.lru_cache но это не поможет здесь, я думаю. Есть идеи?

Теги:
caching

5 ответов

3
Лучший ответ

Создание одноэлементного кеша с временем жизни довольно тривиально:

_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 должна помогите показать вам, как делать большую часть сложных вещей (так как почти все они).

  • 0
    это работает нормально, если вы не хотите кэшировать несколько результатов для разных аргументов функции. Настоятельно рекомендую 1. превратить это в декоратор, чтобы обернуть вокруг функции, выполняющей вычисления, и 2. превратить этот декоратор в экземпляр класса с реализацией __call__ , чтобы способ сохранения состояния кэша скрывался внутри переменных-членов объекта а не глобальные.
  • 0
    @ BowlingHawk95 Конечно, сначала я написал простейшую версию, потому что именно об этом явно просил ОП.
Показать ещё 3 комментария
5

Обычно декоратор прекрасно это понимает

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
  • 0
    Рекурсивная установка с partial является умной, но IMO более уродливой, чем просто наличие другого уровня замыкания. Теперь вы разрешаете вызывать cache двумя разными способами: cache(time_to_live=___)(function) или cache(function, time_to_live=___) . Просто вопрос вкуса, и мне нравится решение.
  • 0
    Вы могли бы действительно назвать это как @cache или @cache(time_to_live=1000) ... но да, у вас есть хорошая точка зрения ... мне не нравятся вложенные функции ... и вообще, мне нравится это как решение, поэтому я всегда использую это при приеме kwargs для декоратора (я НЕ @cache(1000) и хочу, чтобы это не разрешалось)
Показать ещё 7 комментариев
1

Создайте функцию, которая действует как кеш, назовем ее 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)
0

Решение с использованием ring

@ring.lru(expire=60*60)  # seconds
def cached_function(keys):
    return ...

Если вам не нужна политика LRU,

@ring.dict(expire=60*60)  # seconds
def cached_function(keys):
    return ...
0

Декоратор 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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню