[英]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.