是否有一个装饰器来简单地缓存函数返回值?

Tob*_*ias 133 python caching decorator

考虑以下:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name
Run Code Online (Sandbox Code Playgroud)

我是新手,但我认为缓存可能会被装入装饰器中.只有我找不到喜欢它的人;)

PS真正的计算不依赖于可变值

Pao*_*tti 178

从Python 3.2开始,有一个内置的装饰器:

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

Decorator用一个memoizing callable来包装一个函数,它可以节省maxsize最近的调用.当使用相同的参数定期调用昂贵的或I/O绑定函数时,它可以节省时间.

用于计算Fibonacci数的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)
Run Code Online (Sandbox Code Playgroud)

如果你坚持使用Python 2.x,这里有一个其他兼容的memoization库列表:

  • 向后移植 http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/ (2认同)
  • @Jonathan 它有效,但错误。如果我传递一个可散列、可变的参数,并在第一次调用函数后更改对象的值,则第二次调用将返回更改后的对象,而不是原始对象。这几乎肯定不是用户想要的。要使其适用于可变参数,需要“lru_cache”为其缓存的任何结果创建副本,而“functools.lru_cache”实现中不会创建此类副本。当用于缓存大对象时,这样做还会带来难以发现的内存问题的风险。 (2认同)

Nat*_*hen 27

听起来你并不是要求一个通用的memoization装饰器(也就是说,你不想对你想要为不同的参数值缓存返回值的一般情况).也就是说,你想拥有这个:

x = obj.name  # expensive
y = obj.name  # cheap
Run Code Online (Sandbox Code Playgroud)

而通用的memoization装饰器会给你这个:

x = obj.name()  # expensive
y = obj.name()  # cheap
Run Code Online (Sandbox Code Playgroud)

我认为方法调用语法是更好的样式,因为它提示了昂贵计算的可能性,而属性语法建议快速查找.

[更新:我之前链接并引用的基于类的memoization装饰器不适用于方法.我用装饰器函数替换它.]如果你愿意使用通用的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
Run Code Online (Sandbox Code Playgroud)

用法示例:

@memoize
def fibonacci(n):
  if n < 2: return n
  return fibonacci(n - 1) + fibonacci(n - 2)
Run Code Online (Sandbox Code Playgroud)

可以在此处找到另一个具有高速缓存大小限制的memoization装饰器.

  • 我认为如果args不耐用,你可能会遇到问题. (2认同)
  • @SiminJie 装饰器仅被调用一次,它返回的包装函数与用于所有不同调用“fibonacci”的函数相同。该函数始终使用相同的“memo”字典。 (2认同)

acm*_*ght 22

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
Run Code Online (Sandbox Code Playgroud)

样品用途:

>>> @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'}
Run Code Online (Sandbox Code Playgroud)

  • 如果使用关键字参数,此解决方案将返回 TypeError,例如 foo(3, b=5) (2认同)
  • 解决方案的问题是它没有内存限制。至于命名参数,您可以将它们添加到 __ call__ 和 __ missing__ 就像 **nargs (2认同)

Imr*_*ran 9

Werkzeug有一个cached_property装饰者(docs,source)


小智 9

我编写了这个简单的装饰器类来缓存函数响应.我发现它对我的项目非常有用:

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
Run Code Online (Sandbox Code Playgroud)

用法很简单:

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))
Run Code Online (Sandbox Code Playgroud)


Dro*_*man 8

尝试 joblib https://joblib.readthedocs.io/en/latest/memory.html

from joblib import Memory

# customize the decorator
memory = Memory(cachedir=cachedir, verbose=0)

@memory.cache
def f(x):
    print('Running f(%s)' % x)
    return x
Run Code Online (Sandbox Code Playgroud)


vaa*_*aab 6

免责声明:我是kids.cache的作者.

你应该检查一下kids.cache,它提供了一个@cache适用于python 2和python 3 的装饰器.没有依赖关系,~100行代码.例如,考虑到您的代码,使用起来非常简单,您可以像这样使用它:

pip install kids.cache
Run Code Online (Sandbox Code Playgroud)

然后

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
Run Code Online (Sandbox Code Playgroud)

或者你可以把@cache装饰器放在@property(相同的结果)之后.

在属性上使用缓存称为延迟评估,kids.cache可以做更多的事情(它适用于任何参数,属性,任何类型的方法甚至类的函数...).对于高级用户,kids.cache支持cachetools为python 2和python 3(LRU,LFU,TTL,RR缓存)提供精美的缓存存储.

重要说明:默认缓存存储kids.cache是一个标准的dict,不推荐用于长期运行的程序,因为它会导致不断增长的缓存存储.对于此用法,您可以使用例如插入其他缓存存储(@cache(use=cachetools.LRUCache(maxsize=2))以装饰您的函数/属性/类/方法...)


Cir*_*四事件 6

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)
Run Code Online (Sandbox Code Playgroud)

将类的方法转换为属性,该属性的值将被计算一次,然后在实例生命周期中作为常规属性进行缓存。与property()类似,但增加了缓存。对于实例有效的不可变的昂贵的计算属性很有用。

例:

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)
Run Code Online (Sandbox Code Playgroud)

3.8版的新功能。

注意此装饰器要求每个实例上的dict属性都是可变映射。这意味着它不适用于某些类型,例如元类(因为类型实例上的dict属性是类名称空间的只读代理),以及那些指定槽位但不将dict用作已定义槽位之一的类(例如此类)根本不提供dict属性)。


Alp*_*ren 6

functools.cache已在 Python 3.9 (文档) 中发布:

from functools import cache

@cache
def factorial(n):
    return n * factorial(n-1) if n else 1
Run Code Online (Sandbox Code Playgroud)

在以前的 Python 版本中,早期的答案之一仍然是一个有效的解决方案,将其lru_cache用作没有限制和 lru 功能的普通缓存。(文档

如果 maxsize 设置为 None,则禁用 LRU 功能,缓存可以无限制地增长。

这是它的一个更漂亮的版本:

cache = lru_cache(maxsize=None)

@cache
def func(param1):
   pass
Run Code Online (Sandbox Code Playgroud)


Ken*_*old 5

啊,只需找到正确的名称:" 懒惰的财产评估 ".

我这样做也很多; 也许我会在我的代码中使用那个食谱.


Den*_*aia 5

Python Wiki 上还有另一个memoize装饰器的示例:

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

这个例子有点聪明,因为如果参数是可变的,它不会缓存结果。(检查该代码,它非常简单且有趣!)