有没有简单缓存函数返回值的装饰器?

268 投票
20 回答
226877 浏览
提问于 2025-04-15 11:21

考虑以下内容:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

我刚入门,但我觉得可以把缓存的部分提取出来,做成一个装饰器。不过我没有找到这样的装饰器;)

附注:实际的计算不依赖于可变的值。

20 个回答

58

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
302

从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,这里有一些其他兼容的记忆化库:

61

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 属性)。

撰写回答