Python 解释这种高阶函数行为
有人能解释为什么版本1和版本2以相同的速度执行吗?我预计版本2、3和4将花费大约相同的时间Python 解释这种高阶函数行为,python,decorator,Python,Decorator,有人能解释为什么版本1和版本2以相同的速度执行吗?我预计版本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 KeyEr
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)
结果:
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
version 1:4.95815300941
版本2:4.94982290268
版本3:0.000107049942017
版本4:0.0001189707070770972
您的memoize
函数实际上并没有将fib
替换为memo\u fib
,它只是返回一个新函数
这个新函数仍然递归调用原始的、未记忆的fib
所以,基本上,你只是在回忆最高层
在
fib
中,对fib
的递归调用仅使用模块全局名称。(函数基本上与任何其他类型的值没有区别,函数名称与任何其他类型的名称没有区别,因此,如果在模块全局级别定义函数,它就是这样做的。例如,如果您使用dis.dis(fib)
反汇编字节码,您将在名称fib
上看到一个LOAD\u global
)
因此,简单的解决方法是:
fib = memoize(fib)
或者只需使用memoize
作为装饰器,使其更难出错
换句话说,你的例子3和4
或者,更简单地说,使用内置的装饰器。(请注意其文档中的第二个示例。)
如果你真的想偷偷摸摸:在函数体中定义
fib
。它最终将引用fib
作为定义范围中的闭合单元,而不是全局(LOAD\u DEREF
而不是反汇编中的LOAD\u global
)。然后,您可以进入该范围并替换它的fib
,这意味着您的递归函数现在被“秘密地”记忆(实际的全局fib
没有被记忆,但它递归调用的函数是)和“安全地”(除了通过fib
本身,没有其他人可以引用闭包单元格).您的memoize
函数实际上并没有将fib
替换为memo\u fib
,它只是返回一个新函数
这个新函数仍然递归调用原始的、未记忆的fib
所以,基本上,你只是在回忆最高层
在
fib
中,对fib
的递归调用仅使用模块全局名称。(函数基本上与任何其他类型的值没有区别,函数名称与任何其他类型的名称没有区别,因此,如果在模块全局级别定义函数,它就是这样做的。例如,如果您使用dis.dis(fib)
反汇编字节码,您将在名称fib
上看到一个LOAD\u global
)
因此,简单的解决方法是:
fib = memoize(fib)
或者只需使用memoize
作为装饰器,使其更难出错
换句话说,你的例子3和4
或者,更简单地说,使用内置的装饰器。(请注意其文档中的第二个示例。)
如果你真的想偷偷摸摸:在函数体中定义
fib
。它最终将引用fib
作为定义范围中的闭合单元,而不是全局(LOAD\u DEREF
而不是反汇编中的LOAD\u global
)。然后,您可以进入该范围并替换它的fib
,这意味着您的递归函数现在被“秘密地”记忆(实际的全局fib
没有被记忆,但它递归调用的函数是)和“安全地”(除了通过fib
本身,没有其他人可以引用闭包单元格).在第2版中,您使用不同的名称存储了记忆版本,因此您调用fib的次数与第一个版本中的次数相同。您的调用堆栈如下所示:
memo_fib(35)
fib(35)
fib(34)
fib(33)
fib(33)
等等
因此,在本例中,您实际上没有从备忘录中获得任何好处。在版本2中,您使用不同的名称存储了备忘录版本,因此您调用fib的次数与第一个版本相同。您的调用堆栈如下所示:
memo_fib(35)
fib(35)
fib(34)
fib(33)
fib(33)
等等
因此,在这种情况下,您实际上没有从备忘录中获得任何好处。使用
timeit
模块对代码进行计时。使用timeit
模块对代码进行计时。