【python】记忆和装饰器

记忆的定义

脑

“memoization”一词是由 Donald Michie 在 1968 年提出的。它基于拉丁词备忘录,意思是“被记住”。这不是记忆这个词的拼写错误,尽管在某种程度上它有一些共同点。记忆化是一种用于计算以加速程序的技术。这是通过记住处理输入的计算结果(例如函数调用的结果)来实现的。如果使用相同的输入或具有相同参数的函数调用,则可以再次使用先前存储的结果并且避免不必要的计算。在许多情况下,一个简单的数组用于存储结果,但也可以使用许多其他结构,例如关联数组,在 Perl 中称为哈希或在 Python 中称为字典。

记忆可以由程序员显式编程,但一些编程语言(如 Python)提供了自动记忆函数的机制。

使用函数装饰器记忆

你也可以参考我们关于装饰器的章节。特别是,如果您在理解我们的推理时可能有问题。

在我们之前关于递归函数的章节中,我们制定了一个迭代和一个递归版本来计算斐波那契数。我们已经证明,将数学定义直接实现为如下递归函数具有指数运行时行为:

def fib(n):
    如果 n == 0:
        返回 0
    elif n == 1:
        返回 1
    别的:
        返回 fib(n-1) + fib(n-2) 

我们还提出了一种改进递归版本运行时行为的方法,即通过添加字典来记住函数先前计算的值。这是一个明确使用记忆化技术的例子,但我们并没有这样称呼它。这种方法的缺点是失去了原始递归实现的清晰性和美感。

“问题”在于我们更改了递归 fib 函数的代码。下面的代码不会改变我们的 fib 函数,所以它的清晰度和易读性没有受到影响。为此,我们定义并使用了一个我们称之为 memoize 的函数。memoize() 将函数作为参数。函数 memoize 使用字典“备忘录”来存储函数结果。尽管变量 "memo" 和函数 "f" 是 memoize 的局部变量,但它们通过辅助函数被闭包捕获,该辅助函数由 memoize() 作为引用返回。因此,调用 memoize(fib) 返回对 helper() 的引用,该 helper() 正在执行 fib() 本身会执行的操作以及保存计算结果的包装器。对于整数 'n' fib(n) 只会被调用,如果 n 不在备忘录字典中。如果它在里面,

DEF  memoize的(˚F ):
    备忘录 =  {} 
    DEF 助手(X ):
        如果 X   备忘录:            
            备忘录[ X ]  =  ˚F (X )
        返回 备忘录[ X ]
    返回 辅助
DEF  FIB (Ñ ):
    如果 Ñ  ==  0 :
        返回 0 
    elif  n  ==  1 :
        返回 1 
    else 返回 fib ( n - 1 )  +  fib ( n - 2 ) 
fib  =  memoize ( fib ) 
print ( fib ( 40 ))

输出:

102334155

让我们看一下代码中使用 fib 作为参数调用 memoize 的那一行:

 fib = memoize(fib)

这样做,我们将 memoize 变成了装饰器。有人说 fib 函数是由 memoize() 函数修饰的。我们将用下图说明装饰是如何完成的。第一个图说明了装饰之前的状态,即在我们调用 fib = memoize(fib) 之前。我们可以看到引用它们主体的函数名称:

装饰器的运作方式

执行后 fib = memoize(fib) fib 指向辅助函数的主体,该主体已由 memoize 返回。我们也可以看出,原来 fib 函数的代码,以后只能通过辅助函数的“f”函数才能到达。没有其他方法可以直接调用原始 fib,即没有其他引用。修饰的斐波那契函数在 return 语句 return fib(n-1) + fib(n-2) 中被调用,这意味着 memoize 返回的辅助函数的代码:

 

装饰器的运作方式,第二张图

装饰器上下文中的另一点值得特别注意:我们通常不会为一个用例或函数编写装饰器。我们宁愿将它多次用于不同的功能。所以我们可以想象有更多的函数func1、func2、func3等等,它们也消耗了很多时间。因此,用我们的装饰器函数“memoize”来装饰每一个是有意义的:

fib = memoize(fib)
func1 = memoize(func1)
func2 = memoize(func2)
func3 = memoize(func3)
# 等等

我们还没有使用 Pythonic 的方式来编写装饰器。而不是写声明

 fib = memoize(fib) 

我们应该“装饰”我们的 fib 函数:

@memoize

但是在我们的示例 fib() 中,这一行必须直接位于装饰函数的前面。Pythonic 方式的完整示例现在如下所示:

DEF  memoize的(˚F ):
    备忘录 =  {} 
    DEF 助手(X ):
        如果 X   备忘录:            
            备忘录[ X ]  =  ˚F (X )
        返回 备忘录[ X ]
    返回 辅助
@memoize 
DEF  FIB (Ñ ):
    如果 Ñ  ==  0 :
        返回 0 
    elif  n  ==  1 :
        返回 1
    否则返回 fib ( n - 1 )  +  fib ( n - 2 )
打印( fib ( 40 ))

输出:

102334155

使用可调用类进行记忆

到目前为止还不了解面向对象的人可以毫无问题地跳过这一章。

我们也可以将结果缓存封装在一个类中,如下例所示:

 Memoize : 
    def  __init__ ( self ,  fn ): 
        self fn  =  fn
        自我备忘录 =  {} 
    DEF  __call__ (自, * ARGS ):
        如果 ARGS   自我备忘录:
            自我备忘录[ args ]  =  self fn ( * args )
        返回 self 备忘录[ args ] 
@Memoize 
def  fib ( n ): 
    if  n  ==  0 : 
        return  0 
    elif  n  ==  1 : 
        return  1 
    else : 
        return  fib ( n - 1 )  +  fib ( n - 2 ) 
print ( fib ( 40 ))

输出:

102334155

当我们使用字典时,我们不能使用可变参数,即参数必须是不可变的。

锻炼

秤,公共领域

  • 我们的练习是一个古老的谜语,可以追溯到 1612 年。法国耶稣会士 Claude-Gaspar Bachet 提出了它。我们必须称量 1 到 40 磅的数量(例如糖或面粉)。可以在天平上使用的最少砝码数量是多少?

第一个想法可能是使用 1、2、4、8、16 和 32 磅的重量。这是一个最小的数字,如果我们限制自己在一侧放重物而在另一侧放东西,例如糖。但是可以在秤的两个盘子上放置砝码。现在,我们只需要四个权重,即 1, 3, 9, 27

编写一个 Python 函数 weight(),它计算所需的重量及其在平底锅上的分布,以称量从 1 到 40 的任何数量。

解决方案

我们需要线性组合一章中的函数 linear_combination() 。

def  factor_set (): 
        for  i  in  [ - 1 ,  0 ,  1 ]: 
            for  j  in  [ - 1 , 0 , 1 ]: 
                for  k  in  [ - 1 , 0 , 1 ]: 
                    for  l  in  [ - 1 , 0 , 1 ]:
                        产量 ( i ,  j ,  k ,  l )   
def memoize的(˚F ):
    结果 =  {} 
    DEF 助手(Ñ ):
        如果 ñ   结果:
            结果[ Ñ ]  =  ˚F (Ñ )
        返回 的结果[ Ñ ]
    返回 辅助
@memoize 
DEF  linear_combination (Ñ ): 
    “”,”返回元组 (i,j,k,l) 满足
        n = i*1 + j*3 + k*9 + l*27 """ 权=  ( 1 , 3 ,9 ,27 )
     因子  factors_set ():
       总和 =  0
       对于 范围len个(因素)):
          总和 + = 因素[我]  * 晕死[我]
       如果 总和 ==  Ñ :
          返回 因素 

有了这个,很容易编写我们的函数 weight()。

DEF 称重(磅):
        权重 =  (1 , 3 , 9 , 27 )
        的标量 =  linear_combination (磅)
        左 =  “”=  “”
         范围len个(标量)):
            如果 标量[我]  ==  - 1 : 
                left  +=  str ( weights [ i ]) +  " " 
            elif 标量[ i ]  ==  1 : 
                right  +=  str ( weights [ i ])  +  " " 
        return  ( left , right ) 
for  i  in  [ 2 ,  3 ,  4 ,  7 ,  8 ,  9 ,  20 ,  40 ]: 
            pans  =  weight ( i ) 
            print ( "Left pan:"  + str ( i )  +  " plus "  +  pans [ 0 ]) 
            print ( "Right pan:"  +  pans [ 1 ]  +  " \n " )    

输出:

左锅:2加1 
右锅:3 
左锅:3加 
右锅:3 
左锅:4 加 
右锅:1 3 
左锅:7 加 3 
右锅:1 9 
左锅:8 加 1 
右锅:9 
左锅:9加 
右锅:9 
左锅:20 加 1 9 
右锅:3 27 
左锅:40+ 
右锅:1 3 9 27 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值