有没有简单缓存函数返回值的装饰器?
考虑以下内容:
@property
def name(self):
if not hasattr(self, '_name'):
# expensive calculation
self._name = 1 + 1
return self._name
我刚入门,但我觉得可以把缓存的部分提取出来,做成一个装饰器。不过我没有找到这样的装饰器;)
附注:实际的计算不依赖于可变的值。
20 个回答
functools.cache
在 Python 3.9 中发布了(文档):
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
在之前的 Python 版本中,早期的一个回答 仍然是一个有效的解决方案:可以把 lru_cache
当作普通的缓存来用,不设置限制和 LRU 特性。(文档)
如果将 maxsize 设置为 None,LRU 特性就会被禁用,缓存可以无限增长。
这里有一个更好看的版本:
cache = lru_cache(maxsize=None)
@cache
def func(param1):
pass
从Python 3.2开始,有一个内置的装饰器:
@functools.lru_cache(maxsize=100, typed=False)
这个装饰器可以把一个函数包装起来,使用一种叫做“记忆化”的方法,保存最近最多maxsize次的调用结果。当一个耗时或需要输入输出的函数经常用相同的参数调用时,这可以节省时间。
下面是一个计算斐波那契数的LRU缓存示例:
from functools import lru_cache
@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,这里有一些其他兼容的记忆化库:
Python 3.8 的 functools.cached_property
装饰器
https://docs.python.org/dev/library/functools.html#functools.cached_property
在 Werkzeug 中提到了 cached_property
,具体可以参考这个链接:https://stackoverflow.com/a/5295190/895245,不过一个衍生版本将会合并到 3.8 里,这真是太棒了。
这个装饰器可以看作是给 @property
加了缓存,或者说是一个更简洁的 @functools.lru_cache
,适用于没有参数的情况。
文档中说:
@functools.cached_property(func)
把一个类的方法变成一个属性,这个属性的值只会计算一次,然后作为普通属性在实例的生命周期内被缓存。它和
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)
这是 3.8 版本中新加入的功能。
注意:这个装饰器要求每个实例的 dict 属性是可变的映射。这意味着它不适用于某些类型,比如元类(因为元类实例上的 dict 属性是类命名空间的只读代理),还有那些定义了 slots 但没有把 dict 包含在定义的槽中的类(因为这些类根本就没有 dict 属性)。