Есть ли декоратор, чтобы просто кэшировать возвращаемые значения функции?

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

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

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

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

Ответ 1

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

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

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

Пример кэша 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, вот список других совместимых библиотек memoization:

Ответ 2

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

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

в то время как декоратор памятования общего назначения даст вам следующее:

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

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

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

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)

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

Ответ 3

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'}

Ответ 5

Я закодировал этот простой класс декоратора для ответов на кеш-функции. Я считаю, что это очень полезно для моих проектов:

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

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

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

pip install kids.cache

затем

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

Пример:

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

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

Ответ 10

Наряду с Memoize Example я нашел следующие пакеты python:

  • cachepy; Он позволяет настроить ttl и\или количество вызовов для кешированных функций; Кроме того, можно использовать зашифрованный кеш файл...
  • percache

Ответ 11

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

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

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

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

Ответ 12

Я реализовал что-то вроде этого, используя pickle for persistance и используя sha1 для коротких почти-безусловно-уникальных идентификаторов. В основном кеш хэшировал код функции и историю аргументов, чтобы получить sha1, а затем искал файл с этим sha1 в имени. Если он существует, он открыл его и вернул результат; если нет, он вызывает функцию и сохраняет результат (необязательно только сохранение, если потребуется определенное количество времени для обработки).

Тем не менее, я бы поклялся, что нашел существующий модуль, который сделал это, и нашел себя здесь, пытаясь найти этот модуль... Самое близкое, что я могу найти, это то, что выглядит правильно: http://chase-seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

Единственная проблема, с которой я вижу, - это плохо работает для больших входов, поскольку она hashes str (arg), которая не уникальна для гигантских массивов.

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

Ответ 13

@lru_cache не идеален со значениями функций по умолчанию

мой mem декоратор:

import inspect


def get_default_args(f):
    signature = inspect.signature(f)
    return {
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    }


def full_kwargs(f, kwargs):
    res = dict(get_default_args(f))
    res.update(kwargs)
    return res


def mem(func):
    cache = dict()

    def wrapper(*args, **kwargs):
        kwargs = full_kwargs(func, kwargs)
        key = list(args)
        key.extend(kwargs.values())
        key = hash(tuple(key))
        if key in cache:
            return cache[key]
        else:
            res = func(*args, **kwargs)
            cache[key] = res
            return res
    return wrapper

и код для тестирования:

from time import sleep


@mem
def count(a, *x, z=10):
    sleep(2)
    x = list(x)
    x.append(z)
    x.append(a)
    return sum(x)


def main():
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5, z=6))
    print(count(1,2,3,4,5, z=6))
    print(count(1))
    print(count(1, z=10))


if __name__ == '__main__':
    main()

результат - только 3 раза со сном

но с @lru_cache это будет 4 раза, потому что это:

print(count(1))
print(count(1, z=10))

будет рассчитан дважды (плохо работает со значениями по умолчанию)

Ответ 14

Попробуйте joblib http://pythonhosted.org/joblib/memory.html

from joblib import Memory
memory = Memory(cachedir=cachedir, verbose=0)
@memory.cache
    def f(x):
        print('Running f(%s)' % x)
        return x

Ответ 15

Если вы используете Django и хотите кэшировать представления, см. Ответ Нихила Кумара.


Но если вы хотите кэшировать ЛЮБЫЕ результаты функции, вы можете использовать django-cache-utils.

Он использует кеши Django и предоставляет простой в использовании cached декоратор:

from cache_utils.decorators import cached

@cached(60)
def foo(x, y=0):
    print 'foo is called'
    return x+y

Ответ 16

Python 3.8 cached_property декоратор

https://docs.python.org/dev/library/functools.html#functools.cached_property

cached_property был упомянут по адресу: fooobar.com/info/64342/... но он будет объединен в 3.8, что является потрясающим.

Этот декоратор может рассматриваться как кеширующий @property или как уборщик @functools.lru_cache когда у вас нет аргументов.

Документы говорят:

@functools.cached_property(func)

Преобразуйте метод класса в свойство, значение которого вычисляется один раз, а затем кэшируется как обычный атрибут в течение срока службы экземпляра. Аналогично свойству(), с добавлением кэширования. Полезно для дорогих вычисляемых свойств экземпляров, которые в противном случае эффективно неизменяемы.

Пример:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

Новое в версии 3.8.

Примечание. Этот декоратор требует, чтобы атрибут dict в каждом экземпляре был изменяемым отображением. Это означает, что он не будет работать с некоторыми типами, такими как метаклассы (поскольку атрибуты dict в экземплярах типов являются прокси-серверами только для чтения для пространства имен класса), и те, которые определяют слоты, не включая dict в качестве одного из определенных слотов (как такие классы не предоставлять атрибут dict вообще).