Есть ли декоратор для простого кэширования возвращаемых значений функции? - PullRequest
124 голосов
/ 02 мая 2009

Рассмотрим следующее:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

Я новичок, но я думаю, что кэширование может быть преобразовано в декоратор Только я такого не нашел;)

PS реальный расчет не зависит от изменяемых значений

Ответы [ 16 ]

162 голосов
/ 13 марта 2012

Начиная с Python 3.2 есть встроенный декоратор:

@functools.lru_cache(maxsize=100, typed=False)

Декоратор, чтобы обернуть функцию запоминающимся вызовом, который сохраняет до максимума самые последние вызовы. Это может сэкономить время, когда дорогая или связанная с вводом / выводом функция периодически вызывается с одинаковыми аргументами.

Пример кэша LRU для вычисления чисел Фибоначчи :

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> print([fib(n) for n in range(16)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> print(fib.cache_info())
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Если вы застряли с Python 2.x, вот список других совместимых библиотек памятки:

26 голосов
/ 02 мая 2009

Звучит так, будто вы не запрашиваете универсальный декоратор памятки (т. Е. Вас не интересует общий случай, когда вы хотите кэшировать возвращаемые значения для различных значений аргумента). То есть вы хотели бы иметь это:

x = obj.name  # expensive
y = obj.name  # cheap

в то время как универсальный декоратор напоминаний даст вам следующее:

x = obj.name()  # expensive
y = obj.name()  # cheap

Я утверждаю, что синтаксис вызова метода - лучший стиль, потому что он предполагает возможность дорогостоящих вычислений, в то время как синтаксис свойства предлагает быстрый поиск.

[Обновление: декоратор памятки на основе классов, на который я ссылался и который цитировал здесь ранее, не работает для методов. Я заменил его функцией декоратора.] Если вы хотите использовать универсальный декоратор для запоминания, вот простой:

def memoize(function):
  memo = {}
  def wrapper(*args):
    if args in memo:
      return memo[args]
    else:
      rv = function(*args)
      memo[args] = rv
      return rv
  return wrapper

Пример использования:

@memoize
def fibonacci(n):
  if n < 2: return n
  return fibonacci(n - 1) + fibonacci(n - 2)

Другой декоратор памятки с ограничением размера кеша можно найти здесь .

20 голосов
/ 21 марта 2014
class memorize(dict):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        return self[args]

    def __missing__(self, key):
        result = self[key] = self.func(*key)
        return result

Пример использования:

>>> @memorize
... def foo(a, b):
...     return a * b
>>> foo(2, 4)
8
>>> foo
{(2, 4): 8}
>>> foo('hi', 3)
'hihihi'
>>> foo
{(2, 4): 8, ('hi', 3): 'hihihi'}
9 голосов
/ 14 марта 2011

Werkzeug имеет cached_property декоратор ( документы , источник )

7 голосов
/ 08 июня 2015

Я кодировал этот простой класс декоратора для кэширования ответов функций. Я нахожу это ОЧЕНЬ полезным для моих проектов:

from datetime import datetime, timedelta 

class cached(object):
    def __init__(self, *args, **kwargs):
        self.cached_function_responses = {}
        self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))

    def __call__(self, func):
        def inner(*args, **kwargs):
            max_age = kwargs.get('max_age', self.default_max_age)
            if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
                if 'max_age' in kwargs: del kwargs['max_age']
                res = func(*args, **kwargs)
                self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
            return self.cached_function_responses[func]['data']
        return inner

Использование просто:

import time

@cached
def myfunc(a):
    print "in func"
    return (a, datetime.now())

@cached(default_max_age = timedelta(seconds=6))
def cacheable_test(a):
    print "in cacheable test: "
    return (a, datetime.now())


print cacheable_test(1,max_age=timedelta(seconds=5))
print cacheable_test(2,max_age=timedelta(seconds=5))
time.sleep(7)
print cacheable_test(3,max_age=timedelta(seconds=5))
6 голосов
/ 27 апреля 2015

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я автор kids.cache .

Вы должны проверить kids.cache, он предоставляет декоратор @cache, который работает на python 2 и python 3. Никаких зависимостей, ~ 100 строк кода. Использовать его очень просто, например, с учетом вашего кода, вы можете использовать его так:

pip install kids.cache

Тогда

from kids.cache import cache
...
class MyClass(object):
    ...
    @cache            # <-- That's all you need to do
    @property
    def name(self):
        return 1 + 1  # supposedly expensive calculation

Или вы можете поставить декоратор @cache после @property (тот же результат).

Использование кэша в свойстве называется Ленивая оценка , kids.cache может сделать гораздо больше (он работает с функцией с любыми аргументами, свойствами, любым типом методов и даже классами ...). Для продвинутых пользователей kids.cache поддерживает cachetools, который предоставляет модные хранилища кеша для python 2 и python 3 (LRU, LFU, TTL, RR cache).

ВАЖНОЕ ПРИМЕЧАНИЕ : хранилище кэша по умолчанию kids.cache - это стандартный dict, который не рекомендуется для долго выполняющейся программы с разными запросами, так как это приведет к постоянно растущему хранилищу кэширования. Для этого вы можете подключить другие хранилища кеша, используя, например (@cache(use=cachetools.LRUCache(maxsize=2)) для украшения вашей функции / свойства / класса / метода ...)

5 голосов
/ 03 мая 2009

Ах, просто нужно было найти правильное имя для этого: " Ленивая оценка свойства ".

Я тоже так много делаю; возможно я когда-нибудь воспользуюсь этим рецептом в своем коде.

3 голосов
/ 09 мая 2013

Если вы используете Django Framework, у него есть такое свойство для кэширования представления или ответа API используя @cache_page(time), возможны и другие варианты.

Пример:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

Более подробную информацию можно найти здесь .

3 голосов
/ 14 января 2010

В Python Wiki есть еще один пример memoize декоратора:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

Этот пример немного умен, потому что он не будет кэшировать результаты, если параметры изменчивы. (проверьте этот код, он очень простой и интересный!)

2 голосов
/ 08 ноября 2018

Существует fastcache , что означает "реализация C Python 3 functools.lru_cache. Обеспечивает ускорение в 10-30 раз по сравнению со стандартной библиотекой."

То же, что и выбранный ответ , только другой импорт:

from fastcache import lru_cache
@lru_cache(maxsize=128, typed=False)
def f(a, b):
    pass

Кроме того, он устанавливается в Anaconda , в отличие от functools, которые должны быть установлены .

...