Python中利用函数装饰器实现备忘功能
Python中利用函数装饰器实现备忘功能
“备忘”的定义
“memoization”(备忘)这个词是由Donald Michie在1968年提出的,它基于拉丁语单词“memorandum”(备忘录),意思是“被记住”。虽然它和单词“memorization”在某种程度上有些相似,但它并不是该单词的错误拼写。实际上,Memoisation是一种用于通过计算来加速程序的技术,它通过记住输入量的计算结果,例如函数调用结果,来实现其加速目的。如果遇到相同的输入或者具有相同参数的函数调用,那么之前存储的结果就可以被再次使用,从而避免一些不必要的计算。在很多情况下,可以使用一个简单的数组来存储结果,但也可以使用许多其他的数据结构,例如关联数组,它在Perl语言中叫做哈希,在Python语言中称为字典。
备忘功能可以由程序员显式地编程实现,但是一些编程语言如Python,都提供了自动备忘函数的机制。
利用函数装饰器实现备忘功能
在前面关于递归函数的那章中,我们分别使用迭代和递归实现了斐波纳契数列的求解。我们已经证明,如果直接利用斐波纳契数列的数学定义,在一个递归函数中实现数列的求解,正如下面的函数一样,那么它将具有指数级的时间复杂度:
1
2
3
4
5
6
7
|
def fib(n): if n = = 0 : return 0 elif n = = 1 : return 1 else : return fib(n - 1 ) + fib(n - 2 ) |
此外,我们还提出了一种提高递归实现的时间复杂度的方法,即通过添加一个字典来记住之前函数的计算结果。这是一个显式地使用备忘技术的例子,只是当时我们并没有这么称呼它。这种方法的缺点是,原始递归实现的明晰性和优雅性丢失了。
造成以上缺点的原因是,我们改变了递归函数fib的代码。不过下面的代码不会改变我们的fib函数,所以它的明晰性和易读性并没有丢失。为了实现该目的,我们使用自定义的函数memoize()。函数memoize()以函数作为参数,并使用一个字典“memo”来存储函数的结果。虽然变量“memo”和函数“f”仅仅具有局部备忘功能,但是它们通过函数“helper”被一个闭包捕获,而memoize()将函数“helper”作为引用返回。所以,对memoize(fib)的调用将会返回一个helper()的引用,而在helper()中实现了fib()函数的功能以及一个用于保存还未存储的结果到字典“memo”中的包装器,并防止重新计算“memo”中已有的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def memoize(f): memo = {} def helper(x): if x not in memo: memo[x] = f(x) return memo[x] return helper def fib(n): if n = = 0 : return 0 elif n = = 1 : return 1 else : return fib(n - 1 ) + fib(n - 2 ) fib = memoize(fib) print (fib( 40 )) |
现在让我们了解下所谓的装饰器,首先看一下上面代码中将备忘功能指派到fib函数的这一行:
1
|
fib = memoize(fib) |
一种说法是,函数memoize()装饰了函数fib。
将Memoize封装成类
我们还可以将结果的缓存封装到一个类中,如下面的例子所示:
1
2
3
4
5
6
7
8
|
class Memoize: def __init__( self , fn): self .fn = fn self .memo = {} def __call__( self , * args): if args not in self .memo: self .memo[args] = self .fn( * args) return self .memo[args] |
因为我们使用了字典,所以不能使用可变参数,即参数必须是不可变的。
Python中的装饰器
Python中的装饰器是一个可调用的Python对象,用于修改一个函数、方法或者类的定义。原始的对象,也就是即将被改变的那个对象,作为参数传递给一个装饰器,而装饰器则返回一个修改过的对象,例如一个修改过的函数,它会被绑定到定义中使用的名字上。Python中的装饰器与Java中的注解有一个相似的语法,即Python中的装饰器语法可以看作是纯粹的语法糖,使用“@”作为关键字。
示例:使用装饰器实现备忘功能
其实,前面我们已经使用了装饰器,只是没有这么称呼它而已。实际上,本章开头例子中的memoize函数就是一个装饰器,我们使用它来记住fib函数的结果,只是我们没有使用Python中装饰器特殊的语法而已,即艾特字符“@”。
相比于写成下面的形式
1
|
fib = memoize(fib) |
我们可以这样写
1
|
@memoize |
但这一行必须直接写在被装饰的函数之前,在我们的例子fib()中,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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 = = 0 : return 0 elif n = = 1 : return 1 else : return fib(n - 1 ) + fib(n - 2 ) #fib = memoize(fib) print (fib( 40 )) |
利用装饰器检查参数
在讲解递归函数的那章中我们介绍了阶乘函数,在那里我们希望保持函数尽可能简单,而不想掩盖基本理念,所以代码中没有包含任何参数检查代码。然而,如果别人以负数或者浮点数作为参数来调用我们的函数,那么函数将会陷入一个死循环。
下面的程序使用一个装饰器函数来确保传给函数“factorial”的参数是一个正整数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def argument_test_natural_number(f): def helper(x): if type (x) = = int and x > 0 : return f(x) else : raise Exception( "Argument is not an integer" ) return helper @argument_test_natural_number def factorial(n): if n = = 1 : return 1 else : return n * factorial(n - 1 ) for i in range ( 1 , 10 ): print (i, factorial(i)) print (factorial( - 1 )) |
练习
1、我们的练习是一个古老的谜题。1612年,法国耶稣会士Claude-Gaspar Bachet提出了该谜题,即使用一个天平称出从1磅到40磅的所有整数重量的东西(例如,糖或者面粉),求最少的砝码数量。
第一个方法可能是使用1、2、4、8、16和32磅重量的这些砝码。如果我们将砝码放在天平的一端,而将物品放在另一端,那么这种方法用到的砝码数量将是最小的。然而,我们也可以将砝码同时放在天平的两端,此时我们仅仅需要重量为1、3、9、27的砝码。
编写一个Python函数weigh(),该函数计算需要的砝码以及它们在天平盘中的分布,以此来称量1磅到40磅中任何一个整数重量的物品。
解决方法
1、我们需要前面章节“Linear Combinations”中的函数linear_combination()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
def factors_set(): factors_set = ( (i,j,k,l) for i in [ - 1 , 0 , 1 ] for j in [ - 1 , 0 , 1 ] for k in [ - 1 , 0 , 1 ] for l in [ - 1 , 0 , 1 ]) for factor in factors_set: yield factor def memoize(f): results = {} def helper(n): if n not in results: results[n] = f(n) return results[n] return helper @memoize def linear_combination(n): """ returns the tuple (i,j,k,l) satisfying n = i*1 + j*3 + k*9 + l*27 """ weighs = ( 1 , 3 , 9 , 27 ) for factors in factors_set(): sum = 0 for i in range ( len (factors)): sum + = factors[i] * weighs[i] if sum = = n: return factors |
2、利用上面的代码,就能很容易写出我们的函数weigh()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def weigh(pounds): weights = ( 1 , 3 , 9 , 27 ) scalars = linear_combination(pounds) left = "" right = "" for i in range ( len (scalars)): if scalars[i] = = - 1 : left + = str (weights[i]) + " " elif scalars[i] = = 1 : right + = str (weights[i]) + " " return (left,right) for i in [ 2 , 3 , 4 , 7 , 8 , 9 , 20 , 40 ]: pans = weigh(i) print ( "Left pan: " + str (i) + " plus " + pans[ 0 ]) print ( "Right pan: " + pans[ 1 ] + "n" ) |
Python中利用函数装饰器实现备忘功能的更多相关文章
- Python中的@函数装饰器到底是什么?
在解释@函数装饰器之前,先说一下,类中的类方法和静态方法. 在Python中完全支持定义类方法.静态方法.这两种方法很相似,Python它们都使用类来调用(ps:用对象调用也可以). 区别在于:Pyt ...
- python 中多个装饰器的执行顺序
python 中多个装饰器的执行顺序: def wrapper1(f1): print('in wrapper1') def inner1(*args,**kwargs): print('in inn ...
- 第7.26节 Python中的@property装饰器定义属性访问方法getter、setter、deleter 详解
第7.26节 Python中的@property装饰器定义属性访问方法getter.setter.deleter 详解 一. 引言 Python中的装饰器在前面接触过,老猿还没有深入展开介绍装饰 ...
- python语言中的函数装饰器
装饰器 什么是装饰器? 装饰:给已有的对象(函数)添加新的功能 器:工具 在python中指具备某些功能的函数 装饰器:装饰器就是一个给其他函数增加功能的函数 一种设计原则: ...
- Python作用域-->闭包函数-->装饰器
1.作用域: 在python中,作用域分为两种:全局作用域和局部作用域. 全局作用域是定义在文件级别的变量,函数名.而局部作用域,则是定义函数内部. 关于作用域,我要理解两点:a.在全局不能访问到局部 ...
- python二 总结--函数-- 装饰器
装饰器是什么? 有什么用? 为什么要用? 真的有用吗? 1.装饰器: 装饰器: 定义:本质是函数,(装饰其他函数)就是为其他函数添加附加功能. 原则:1.不能修改被装饰的函数的源代码 ...
- python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)
转载:http://python.jobbole.com/81683/ 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需 ...
- python语法基础-函数-装饰器-长期维护
######################################################### # 装饰器 # 装饰器非常重要,面试Python的公司必问, # 原则:开放封闭原则 ...
- python 修改的函数装饰器
把好的代码记录下来 方便以后学习 修改的函数参数装饰器 from functools import wraps import time import logging def warn(timeout) ...
随机推荐
- Ubuntu You don't have permission to access解决方案!
最近对Linux越来越喜欢了,就直接安装了一个Ubuntu,配制好LAMP后,在做小项目时,出现了下面的问题:Ubuntu You don't have permission to access ** ...
- WebApi Gzip(Deflate) 压缩请求数据
由于不能直接访问指定数据库,只能通过跳板机查询Oracle数据,所以要做一个数据中转接口, 查询数据就要压缩,于是就找资料,代码如下,其中要注意的是Response.Headers.Remove(&q ...
- [Forward]Visual Guide: Setting up My Sites in SharePoint 2013
from http://blog.sharedove.com/adisjugo/index.php/2012/07/25/visual-guide-setting-up-my-sites-in-sh ...
- IE8一枝独秀的JS兼容BUG
// 例如淡入淡出的封装类文件 function ImagesEff(div,time){ this.arr=[];//装载所有div this.time=time; this.recordOld=n ...
- Linux on Power 上的调试工具和技术
Linux on Power 上的调试工具和技术 简介: 调试是一项主要的软件开发活动,作为应用程序开发人员,您无法避免对程序进行调试.有效的调试不仅能缩短软件开发周期,而且可以节省成本.本文简要介 ...
- hiho #1044 : 状态压缩·一
描述 小Hi和小Ho在兑换到了喜欢的奖品之后,便继续起了他们的美国之行,思来想去,他们决定乘坐火车前往下一座城市——那座城市即将举行美食节! 但是不幸的是,小Hi和小Ho并没有能够买到很好的火车票—— ...
- 芭比娃娃 Barbara
芭比娃娃(芭比)是20世纪最广为人知及最畅销的玩偶,由Ruth Handler发明,于1959年3月9日举办的美国国际玩具展览会(American International Toy Fair)上首次 ...
- 《Linear Algebra and Its Applications》-chaper3-行列式-行列式初等变换
承接上一篇文章对行列式的引入,这篇文章将进一步记录关于行列式的有关内容,包括如下的几个方面: (1)行列式3个初等变换的证明. (2)转置行列式与原行列式相等的证明. (3)定理det(AB) = d ...
- OpenWrt简要刷机教程
准备工作 1. 下载openwrt中文固件到PC.(当然其他英文固件也可) 2 找到路由器的RST键. 3 找到路由器刷机口---姑且称之为“WAN口” 4. 关闭路由器的电源. 5. 将PC网口 ...
- selenium grid 测试资料
像风一样自由的4篇博客: http://blog.csdn.net/five3/article/details/9671287 http://blog.csdn.net/five3/article/d ...