什么是记忆化,我如何在Python中使用它?

498

我刚刚开始学习Python,对于“记忆化”的概念和使用方法一无所知。能否给出一个简单的示例呢?


283
当相关维基百科文章的第二个句子包含“在一般自顶向下的解析算法中相互递归地进行下降解析[1],并在多项式时间和空间内处理模糊性和左递归[2][3]”这一短语时,我认为向SO询问正在发生什么是完全合适的。 - Clueless
14
@Clueless:该短语前面有“记忆化也被用于其他情境(并非仅为了提高速度),例如”。因此,这只是一些例子的列表(无需理解);它不是关于记忆化解释的一部分。 - ShreevatsaR
2
由于pycogsci.info已经关闭,这里提供一个新的PDF文件链接:http://people.ucsc.edu/~abrsvn/NLTK_parsing_demos.pdf - Stefan Gruenwald
1
看到有多少人回答并仍在回答这个问题,让我相信了“自行车棚效应”https://en.wikipedia.org/wiki/Law_of_triviality。 - A_P
@A_P 实际上,在你写这篇文章的时候,除了一个答案是最近的(2016年),其他13个答案都是5年前的(2014年)。不确定这是否算作“仍在回答”。我刚刚发布的答案增加了速度考虑因素,而其他答案中我没有看到这些因素,并且并未实现新的方法或任何东西。当然有你所描述的现象的例子,但我不确定这是否就是它。 - Luc
显示剩余2条评论
14个回答

3

这个解决方案可同时处理位置参数和关键字参数,而不考虑传递关键字参数的顺序(使用inspect.getargspec):

import inspect
import functools

def memoize(fn):
    cache = fn.cache = {}
    @functools.wraps(fn)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
        key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
        if key not in cache:
            cache[key] = fn(**kwargs)
        return cache[key]
    return memoizer

类似问题:如何在Python中识别具有相同可变参数的函数调用以进行记忆化


3
只是想要补充一下已经提供的答案,Python装饰器库有一些简单但有用的实现,也可以记忆“不可哈希类型”,不像functools.lru_cache

2
这个装饰器不进行“不可哈希类型”的记忆化!它只是回退到调用没有记忆化的函数,违背了显式优于隐式的原则。 - ostrokach

2
cache = {}
def fib(n):
    if n <= 1:
        return n
    else:
        if n not in cache:
            cache[n] = fib(n-1) + fib(n-2)
        return cache[n]

1

如果速度是一个考虑因素:

  • @functools.cache@functools.lru_cache(maxsize=None) 一样快, 在我的系统上循环一百万次需要0.122秒(15次运行中最佳结果)
  • 全局缓存变量要慢得多,在我的系统上循环一百万次需要0.180秒(15次运行中最佳结果)
  • self.cache 类变量仍然比较慢,在我的系统上循环一百万次需要0.214秒(15次运行中最佳结果)

后两者的实现方式与当前得票最高的答案中所描述的类似。

这是没有内存耗尽预防措施的情况下,即我没有在classglobal方法中添加代码来限制缓存的大小,这真的是最基本的实现。如果您需要,lru_cache方法可以免费提供此功能。

对我来说一个未解决的问题是如何对具有functools装饰器的内容进行单元测试。是否有可能以某种方式清空缓存?使用类方法(可以为每个测试实例化一个新类)或次要地使用全局变量方法(因为您可以执行yourimportedmodule.cachevariable = {}来清空它)似乎是最干净的单元测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接