简体   繁体   English

解释这种高阶函数的行为

[英]Explain this higher-order function behavior

Can someone explain why version 1 and 2 execute at the same speed? 有人可以解释为什么版本1和2以相同的速度执行吗? I expected versions 2, 3, and 4 to take about the same amount of time. 我期望版本2、3和4花费相同的时间。

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

def memoize(fn):
    stored_results = {}

    def memoized(*args):
        try:
            return stored_results[args]
        except KeyError:
            #nothing cached
            result = stored_results[args] = fn(*args)
            return result

    return memoized

#version 1 (unmemoized)
print timeit.timeit('fib(35)', 'from __main__ import fib', number=1)
print fib, '\n'

#version 2
memo_fib = memoize(fib)
print timeit.timeit('memo_fib(35)', 'from __main__ import memo_fib', number=1)
print memo_fib, '\n'

#version 3 (wrapped)
fib = memoize(fib)
print timeit.timeit('fib(35)', 'from __main__ import fib', number=1)
print fib, '\n'

#version 4 (w/ decoration line)
@memoize
def fib(n):
    return n if n in [0, 1] else fib(n-2)+fib(n-1)

print timeit.timeit('fib(35)', 'from __main__ import fib', number=1)

Results: 结果:

version 1:  4.95815300941
<function fib at 0x102c2b320> 

version 2:  4.94982290268
<function memoized at 0x102c2b410> 

version 3:  0.000107049942017
<function memoized at 0x102c2b488> 

version 4:  0.000118970870972

Your memoize function isn't actually replacing fib with memo_fib , it's just returning a new function. 您的memoize功能实际上并没有用memo_fib代替fib ,它只是返回了一个新函数。

That new function still recursively calls the original, un-memoized fib . 这个新函数仍然递归地调用原始的,未存储的fib

So, basically, you're only memoizing the very top level. 因此,基本上,您只是在记住最高层。


Within fib , the recursive call to fib is just using the module-global name. fib ,递归调用fib只是使用模块的全局名称。 (Functions are basically no different from any other kind of value, and function names no different from any other kind of name, so if you define a function at the module global level, that's what it does. If you, eg, disassemble the bytecode with dis.dis(fib) , you will see a LOAD_GLOBAL on the name fib .) (函数基本上与任何其他类型的值都没有什么不同,函数名称与任何其他类型的名称也没有什么不同,因此,如果您在模块全局级别定义一个函数,它就是这样做的。例如,如果您反汇编字节码,使用dis.dis(fib) ,您将在名称fib上看到一个LOAD_GLOBAL

So, the easy fix is: 因此,简单的解决方法是:

fib = memoize(fib)

Or just use memoize as a decorator, to make this harder to get wrong. 或仅使用memoize作为装饰器,以使其更难于出错。

In other words, your examples 3 and 4. 换句话说,您的示例3和4。

Or, even more simply, use the built-in lru_cache decorator. 或者,甚至更简单地,使用内置的lru_cache装饰器。 (Notice the second example in its documentation.) (请注意其文档中的第二个示例。)


If you want to be really sneaky: Define fib within a function body. 如果你想成为真正的偷偷摸摸:定义fib函数体内。 It will end up referencing fib as a closure cell from the defining scope, rather than a global ( LOAD_DEREF instead of LOAD_GLOBAL in disassembly). 它将最终将fib引用为定义范围中的闭合单元,而不是全局LOAD_GLOBAL在反汇编中为LOAD_DEREF而不是LOAD_GLOBAL )。 You can then reach into that scope and replace its fib , which means that your recursive function is now memoized "secretly" (the actual global fib isn't memoized, but the function it recursively calls is) and "safely" (nobody else has a reference to the closure cell except through fib itself). 然后,您可以进入该范围并替换 fib ,这意味着您的递归函数现在被“秘密”存储(实际的全局fib没有被存储,但是它被递归调用的函数被存储)和“安全”(其他人没有)对闭锁单元的引用(通过fib本身除外)。

In version 2, you've stored the memoized version with a different name, so you wind up calling fib just as many times as in the first version. 在版本2中,您使用不同的名称存储了已记忆的版本,因此您最终调用fib的次数与第一个版本相同。 Your call stack looks like this: 您的调用堆栈如下所示:

memo_fib(35)
    fib(35)
        fib(34)
            fib(33)
        fib(33)

etc. 等等

So you aren't actually receiving any benefit from the memoization in this case. 因此,在这种情况下,您实际上不会从备忘录中获得任何好处。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM