【问题标题】:Why does this memoizer work on recursive functions?为什么这个记忆器在递归函数上工作?
【发布时间】:2014-01-21 10:48:48
【问题描述】:

我不明白为什么下面的代码会使fib 以线性而不是指数时间运行。

def memoize(obj):
    """Memoization decorator from PythonDecoratorLibrary. Ignores
    **kwargs"""

    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        if args not in cache:
            cache[args] = obj(*args, **kwargs)
        return cache[args]
    return memoizer

@memoize
def fib(n):
    return n if n in (0, 1) else fib(n-1) + fib(n-2)

例如,fib(100) 并没有像我预期的那样完全炸毁。

我的理解是@memoize 设置fib = memoize(fib)。所以当你调用fib(100)时,看到100不在缓存中,它会在100上调用obj。但是obj原始的fib 函数,所以它不应该和我们根本没有记忆一样长(在第一次评估中)吗?

【问题讨论】:

    标签: python recursion memoization


    【解决方案1】:

    装饰器中的obj 确实是包装的、未修改的、非记忆的函数。但是,当所述函数尝试递归时,它会查找全局名称fib,获取已记忆的包装函数,因此也会导致第 99、98、...斐波那契数被沿途记忆。

    【讨论】:

      【解决方案2】:

      名称被解析词法。仅仅因为您在名为 fib 的函数中调用了名为 fib 的函数,并不意味着它必然是相同的 fib

      一个(非常不准确的)正在发生的事情的演示是这样的:

      def fib(n):
          return n if n in (0, 1) else globals()['fib'](n-1) + globals()['fib'](n-2)
      

      由于装饰器会影响globals,因此在递归调用发生时您会得到装饰的fib

      【讨论】:

        【解决方案3】:

        “但是 obj 是原始的 fib 函数,所以它不应该像我们根本没有记忆一样长(在第一次评估时)吗?”

        obj (在memoizer 中)确实是原始的fib 函数。诀窍在于,当fib 递归调用自身时,它调用的是memoize(fib)

        def fib(n):
            return n if n in (0, 1) else wrapped(fib(n-1)) + wrapped(fib(n-2))
        

        其中wrapped 是functools 生成的调用memoize.memoizer 的函数。有点。

        递归调用最终可能是在 obj.cache 中的简单查找(理论上 O(1)),这可以显着提高性能。

        【讨论】:

          猜你喜欢
          • 2012-01-22
          • 1970-01-01
          • 2012-11-26
          • 2014-10-31
          • 1970-01-01
          • 2021-10-23
          • 2021-10-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多