将备忘录装饰器理解为闭包

Mic*_*ggs 3 python closures decorator memoization python-decorators

我正在尝试了解Python装饰器,并且想更详细地了解闭包如何应用,例如在此备注上下文中:

def memoize(f):
    memo = {}
    def helper(x):                 
        if x not in memo:
            memo[x] = f(x)
        return memo[x]
    return helper   

@memoize  
def fib(n):
    if n in (0,1):
        return n
    return fib(n - 1) + fib(n - 2)
Run Code Online (Sandbox Code Playgroud)

我了解即使程序流不再位于该封闭范围内,也memoize将返回绑定到memo其封闭范围内的值的函数helper。因此,如果memoize反复调用,它将根据的当前值返回变化的功能序列memo。我也了解这@memoize是语法糖,它导致调用fib(n)被替换为memoize(fib(n))

我苦苦挣扎的是nin 的调用值如何fib(n)有效地转换为xin helper(x)。大多数关于闭包的教程似乎都没有明确指出这一点,或者他们隐约地说一个函数“关闭”了另一个函数,这听起来像是魔术。我可以看到如何使用语法,但是希望更好地掌握这里发生的事情以及代码执行时传递的对象和数据。

Mar*_*ers 5

您误解了@memoize装饰器的功能。每次调用时都不会调用装饰器fib。装饰器每个装饰函数调用一次

fib函数代替helper()功能,具有可作为原始函数对象f闭合,与一起memo闭合:

>>> def fib(n):
...     if n in (0,1):
...         return n
...     return fib(n - 1) + fib(n - 2)
...
>>> fib
<function fib at 0x1023398c0>
>>> memoize(fib)
<function helper at 0x102339758>
Run Code Online (Sandbox Code Playgroud)

这为替换helper函数提供了一个闭包,并且每次您调用该helper()函数时,memo都使用相同的字典来查找已经产生的结果以获取当前值x

您可以在解释器中看到所有这些工作:

>>> @memoize
... def fib(n):
...     if n in (0,1):
...         return n
...     return fib(n - 1) + fib(n - 2)
...
>>> fib
<function helper at 0x10232ae60>
Run Code Online (Sandbox Code Playgroud)

注意那里的函数名称!那是helper。它有一个闭包:

>>> fib.__closure__
(<cell at 0x10232d9f0: function object at 0x10232ade8>, <cell at 0x10232da98: dict object at 0x102353050>)
Run Code Online (Sandbox Code Playgroud)

函数对象和字典,分别是fmemo。使用以下命令访问单元格内容.cell_contents

>>> fib.__closure__[0].cell_contents
<function fib at 0x10232ade8>
>>> fib.__closure__[1].cell_contents
{}
Run Code Online (Sandbox Code Playgroud)

那就是原始的修饰函数,空字典也可以。让我们使用以下功能:

>>> fib(0)
0
>>> fib(1)
1
>>> fib.__closure__[1].cell_contents
{0: 0, 1: 1}
Run Code Online (Sandbox Code Playgroud)

这就是x设置为01存储在memo字典中的结果值。下次我fib使用任何一个值致电时,都会使用备忘录。x计算并添加新值:

>>> fib(2)
1
>>> fib.__closure__[1].cell_contents
{0: 0, 1: 1, 2: 1}
Run Code Online (Sandbox Code Playgroud)

您可以通过计时较大的数字花多长时间来查看其效果:

>>> start = time.clock(); fib(500); print(format(time.clock() - start, '.10f'))
139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125L
0.0008390000
>>> start = time.clock(); fib(500); print(format(time.clock() - start, '.10f'))
139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125L
0.0000220000

>>> start = time.clock(); fib(500); print(format(time.clock() - start, '.10f'))
139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125L
0.0000190000
Run Code Online (Sandbox Code Playgroud)

首次通话后,查找备忘录的速度是所有中间结果被记录的速度的40倍:

>>> len(fib.__closure__[1].cell_contents)
501
Run Code Online (Sandbox Code Playgroud)