“备忘”的定义

“memoization”(备忘)这个词是由Donald Michie在1968年提出的,它基于拉丁语单词“memorandum”(备忘录),意思是“被记住”。虽然它和单词“memorization”在某种程度上有些相似,但它并不是该单词的错误拼写。实际上,Memoisation是一种用于通过计算来加速程序的技术,它通过记住输入量的计算结果,例如函数调用结果,来实现其加速目的。如果遇到相同的输入或者具有相同参数的函数调用,那么之前存储的结果就可以被再次使用,从而避免一些不必要的计算。在很多情况下,可以使用一个简单的数组来存储结果,但也可以使用许多其他的数据结构,例如关联数组,它在Perl语言中叫做哈希,在Python语言中称为字典。

备忘功能可以由程序员显式地编程实现,但是一些编程语言如Python,都提供了自动备忘函数的机制。

利用函数装饰器实现备忘功能

在前面关于递归函数的那章中,我们分别使用迭代和递归实现了斐波纳契数列的求解。我们已经证明,如果直接利用斐波纳契数列的数学定义,在一个递归函数中实现数列的求解,正如下面的函数一样,那么它将具有指数级的时间复杂度:

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”中已有的结果。

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函数的这一行:

fib = memoize(fib)

一种说法是,函数memoize()装饰了函数fib。

将Memoize封装成类

我们还可以将结果的缓存封装到一个类中,如下面的例子所示:

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中装饰器特殊的语法而已,即艾特字符“@”。

相比于写成下面的形式

fib = memoize(fib)

我们可以这样写

@memoize

但这一行必须直接写在被装饰的函数之前,在我们的例子fib()中,如下所示:

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”的参数是一个正整数:

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()。

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()。

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")

fib函数用python编写_Python中利用函数装饰器实现备忘功能相关推荐

  1. python中round函数参数有负数_Python中round函数使用注意事项

    使用round函数的时候发现了一个奇怪的现象.一直觉得round函数是一个用于四舍五入的函数,结果却不一定.一般如果觉得奇怪,那就是没弄懂其本质的运作原理,所以深入了解了下round函数. Pytho ...

  2. python中int函数的用法浅析_Python中int()函数的用法浅析

    int()是Python的一个内部函数 Python系统帮助里面是这么说的 >>> help(int) Help on class int in module __builtin__ ...

  3. 在python中、对于函数定义代码的理解_python中如何理解装饰器代码?

    长文预警,[最浅显易懂的装饰器讲解] 能不能专业地复制题目?配上代码,问题分段. 我来给提主配上问题的代码. 正式回答: 1:如何理解return一个函数,它与return一个值得用法区别在哪? 敲黑 ...

  4. python函数传值还是地址_python中给函数传参是传值还是传引用

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...

  5. python中int函数是什么作用_python中int函数怎么用

    python中int函数怎么用,字符串,函数,数字,出现在,赋值 python中int函数怎么用 易采站长站,站长之家为您整理了python中int函数怎么用的相关内容. int() 函数用于将一个字 ...

  6. python pop函数里有数字_python中pop()函数如何使用

    pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值. 语法:list.pop(obj=list[-1]) //默认为 index=-1,删除最后一个列表值. //obj ...

  7. python中decode函数在哪个库_python中decode函数的使用方法

    python中decode函数的使用方法 发布时间:2020-12-15 09:22:45 来源:亿速云 阅读:75 作者:小新 这篇文章主要介绍python中decode函数的使用方法,文中介绍的非 ...

  8. python两个装饰器执行顺序_python中多个装饰器的执行顺序

    今天讲一下python中装饰器的执行顺序,以两个装饰器为例. 装饰器代码如下: def wrapper_out1(func): print('--out11--') def inner1(*args, ...

  9. python两个装饰器执行顺序_python中多个装饰器的执行顺序详解

    装饰器是程序开发中经常会用到的一个功能,也是python语言开发的基础知识,如果能够在程序中合理的使用装饰器,不仅可以提高开发效率,而且可以让写的代码看上去显的高大上^_^ 使用场景 可以用到装饰器的 ...

最新文章

  1. nagios监控三部曲之——为什么nagios不能发送报警邮件(2)
  2. 前端(HTML/CSS/JS)-CSS编码规范
  3. 运行SSIS包的几种方式
  4. Java中的注解以及应用 @Deprecated @SupressWarning @Override
  5. location驱动包_Zynq SDK 驱动探求(三):论一个外设驱动的全部身家·Xilinx SDK 驱动源码结构...
  6. Volley框架使用及源码解析
  7. JDK8新特性:函数式接口@FunctionalInterface的使用说明
  8. FastTunnel - 打造人人都能搭建的内网穿透工具
  9. [html] 如何在页面引用外部的html页面?
  10. 上海电力学院linux大作业,上海电力电路大作业2重点.docx
  11. 红外图分特点析及红外图像分割
  12. 记vsftpd虚拟用户登录返回530 Login incorrect解决过程
  13. h5判断百度、高德、腾讯地图客户端是否安装及调起客户端导航
  14. 计算机快捷键任务管理器,打开电脑任务管理器快捷键是什么
  15. 新侨快讯:官方公布加拿大时薪最高和最低的工作,你的行业上榜吗?
  16. Windows鼠标右键->新建中添加内容
  17. 去掉字符串头尾指定字符
  18. 2023 年值得关注的 7 大人工智能 (AI) 技术趋势
  19. pycharm更改整体背景颜色(黑-白)
  20. 鸿蒙和ios流畅对比,鸿蒙OS对比iOS,华为再次“超越”,流畅度大幅领先苹果!...

热门文章

  1. Python学习14 模块和包
  2. codeforces 344A-C语言解题报告
  3. 18条工作感想:不要不情愿地工作
  4. 职场新人的入门法则:少想、多做、立即执行!
  5. 消息队列 应用场景 解析
  6. mysql -u root -p 解释
  7. 解决 : Apache Tomcat/8.0.0-RC1 - Error report ... HTTP Status 404
  8. mongodb数据库的备份与恢复
  9. MySQL定时备份(全量备份+增量备份)
  10. HDU - 5919 Sequence II