什么是memoization以及如何在Python中使用它?

blu*_*959 356 python memoization

我刚开始使用Python,我不知道什么是memoization以及如何使用它.另外,我可以有一个简化的例子吗?

jas*_*son 340

记忆有效地指基于方法输入记住("记忆"→"备忘录"→记忆)方法调用的结果,然后返回记住的结果而不是再次计算结果.您可以将其视为方法结果的缓存.有关详细信息,请参阅第387页,了解算法导论(3e)中的定义,Cormen等.

在Python中使用memoization计算阶乘的简单示例如下所示:

factorial_memo = {}
def factorial(k):
    if k < 2: return 1
    if k not in factorial_memo:
        factorial_memo[k] = k * factorial(k-1)
    return factorial_memo[k]
Run Code Online (Sandbox Code Playgroud)

您可以更复杂,并将memoization进程封装到一个类中:

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        #Warning: You may wish to do a deepcopy here if returning objects
        return self.memo[args]
Run Code Online (Sandbox Code Playgroud)

然后:

def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

factorial = Memoize(factorial)
Run Code Online (Sandbox Code Playgroud)

在Python 2.4中添加了一个称为" 装饰器 "的功能,它允许您现在只需编写以下内容即可完成相同的操作:

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)
Run Code Online (Sandbox Code Playgroud)

Python的装饰图书馆有一个名为类似的装饰memoized是不是稍微更强大的是Memoize这里显示类.

  • Memoize类解决方案有问题,它不会像`factorial_memo`一样工作,因为`def factorial`里面的`factorial`仍然会调用旧的unmemoize`factial`. (10认同)
  • 顺便说一句,你也可以写`if不在factorial_memo:`,这比在factorial_memo中的`if not k'更好. (9认同)
  • 应该真的这样做装饰. (5认同)
  • @ durden2.0我知道这是一个旧评论,但`args`是一个元组.`def some_function(*args)`使args成为一个元组. (3认同)
  • 谢谢你的建议.Memoize类是一个优雅的解决方案,可以轻松应用于现有代码,而无需进行太多重构. (2认同)
  • 我认为@dlutxx有一点意义.第一个版本可以在第一次*时快速计算第50个Fibonacci数,而使用Memoize的第二个版本则不能. (2认同)
  • 如果要更改输出,请小心进行深拷贝,否则会无意中更改缓存。 (2认同)
  • 关于记忆化,确实应该说明一条规则。任何函数都可以被记忆,条件是 1) 其确定性,并且 2) 函数没有传入或传出副作用。换句话说,如果函数没有设置,或者依赖于它返回的值和括号中的值之外的值,您可以记忆。如果这不是真的,那就麻烦了。避免副作用通常是良好的编程(并且在像 Haskel 这样的真正的函数式语言中是强制性的),并且会为您节省大量令人心痛的调试。但它并不总是 OO 兼容的,所以要明智地选择。 (2认同)

Fli*_*imm 210

Python 3.2的新功能是functools.lru_cache.默认情况下,它只缓存128个最近使用的来电,但你可以设置maxsizeNone以表明缓存应当永不过期:

import functools

@functools.lru_cache(maxsize=None)
def fib(num):
    if num < 2:
        return num
    else:
        return fib(num-1) + fib(num-2)
Run Code Online (Sandbox Code Playgroud)

这个功能本身很慢,试试fib(36)你必须等待大约十秒钟.

添加lru_cache注释可确保如果最近为特定值调用了函数,则不会重新计算该值,而是使用缓存的先前结果.在这种情况下,它会带来巨大的速度提升,而代码不会被缓存的细节混乱.

  • @Andyk默认的Py3递归限制是1000.第一次调用`fib`时,它需要在发生memoization之前重复到基本情况.所以,你的行为只是预期的. (5认同)
  • 尝试 fib(1000),得到 RecursionError: 比较最大递归深度 (2认同)
  • 如果我没记错的话,它只会缓存直到进程没有被终止,对吗?或者无论进程是否被杀死它都会缓存?例如,假设我重新启动系统 - 缓存的结果是否仍会被缓存? (2认同)
  • 请注意,由于它是递归函数并且正在缓存其自身的中间结果,因此甚至可以加快函数的首次运行速度。最好说明一个非递归函数,这种函数本质上很慢,很难让像我这样的虚拟人更清楚。:D (2认同)
  • 3.9 中的新功能是 [`functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache),它是(在 [cpython](https://github.com/ python/cpython/blob/master/Lib/functools.py#L653)至少)是“lru_cache(maxsize=None)”的包装器,但名称更短。 (2认同)

Nou*_*him 59

其他答案涵盖了很好的内容.我不是在重复.只是一些可能对您有用的要点.

通常,memoisation是一种可以应用于计算某些东西(昂贵)并返回值的任何函数的操作.因此,它通常被实现为装饰器.实现很简单,就像这样

memoised_function = memoise(actual_function)
Run Code Online (Sandbox Code Playgroud)

或表达为装饰者

@memoise
def actual_function(arg1, arg2):
   #body
Run Code Online (Sandbox Code Playgroud)


Bry*_*ley 18

Memoization保持昂贵计算的结果并返回缓存结果而不是连续重新计算它.

这是一个例子:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]
Run Code Online (Sandbox Code Playgroud)

有关记忆维基百科条目中可以找到更完整的描述.


Kar*_*bat 15

让我们不要忘记hasattr那些想要手工制作的内置功能.这样,您可以将mem缓存保留在函数定义中(而不是全局).

def fact(n):
    if not hasattr(fact, 'mem'):
        fact.mem = {1: 1}
    if not n in fact.mem:
        fact.mem[n] = n * fact(n - 1)
    return fact.mem[n]
Run Code Online (Sandbox Code Playgroud)

  • 这似乎是一个非常昂贵的想法。对于每个 n,它不仅缓存 n 的结果,还缓存 2 ... n-1 的结果。 (2认同)

mr.*_*rre 13

我发现这非常有用

def memoize(function):
    from functools import wraps

    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper


@memoize
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci(25)
Run Code Online (Sandbox Code Playgroud)


Rom*_*ter 6

Memoization基本上保存了使用递归算法完成的过去操作的结果,以便在稍后阶段需要相同的计算时减少遍历递归树的需要.

http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

Python中的Fibonacci Memoization示例:

fibcache = {}
def fib(num):
    if num in fibcache:
        return fibcache[num]
    else:
        fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
        return fibcache[num]
Run Code Online (Sandbox Code Playgroud)

  • 为了获得更好的性能,使用前几个已知值预先植入你的fibcache,然后你可以采取额外的逻辑来处理代码的"热路径". (2认同)

Con*_*nal 5

Memoization是将函数转换为数据结构.通常,人们希望转换以递增和懒惰的方式发生(根据给定域元素的要求 - 或"密钥").在惰性函数语言中,这种惰性转换可以自动发生,因此可以在没有(显式)副作用的情况下实现memoization.


yeg*_*gle 5

好吧,我应该首先回答第一部分:什么是记忆?

这只是以时间交换内存的一种方法。想一想乘法表

在Python中使用可变对象作为默认值通常被认为是不好的。但是,如果明智地使用它,则实际上可以实现memoization

这是一个改编自http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects的示例

使用dict函数定义中的可变变量,可以缓存中间计算结果(例如,在计算factorial(10)后进行计算时factorial(9),我们可以重用所有中间结果)

def factorial(n, _cache={1:1}):    
    try:            
        return _cache[n]           
    except IndexError:
        _cache[n] = factorial(n-1)*n
        return _cache[n]
Run Code Online (Sandbox Code Playgroud)