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

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

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

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

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

Ответы [ 16 ]

2 голосов
/ 26 апреля 2016

Вместе с примером Memoize Я нашел следующие пакеты python:

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

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

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

cached_property от Werkzeug упоминается по адресу: https://stackoverflow.com/a/5295190/895245, но предположительно производная версия будет объединена в 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 на экземплярах типов являются только для чтения прокси для пространства имен класса), и те, которые указывают slots без включая dict в качестве одного из определенных слотов (поскольку такие классы вообще не предоставляют атрибут dict ).

1 голос
/ 25 декабря 2018

@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))

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

1 голос
/ 13 сентября 2013

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

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

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

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

0 голосов
/ 16 марта 2018

Если вы используете Django и хотите кешировать представления, см. Nikhil Kumar's answer .


Но если вы хотите кэшировать ЛЮБЫЕ результаты функции, вы можете использовать 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
0 голосов
/ 03 марта 2018

Попробуйте 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
...