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

 更新时间:2015年03月30日 15:11:30   作者:python-course  
这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归、确保参数传递的正确,需要的朋友可以参考下

“备忘”的定义

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

相关文章

  • 用Python画小女孩放风筝的示例

    用Python画小女孩放风筝的示例

    今天小编就为大家分享一篇用Python画小女孩放风筝的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • Pandas读取并修改excel的示例代码

    Pandas读取并修改excel的示例代码

    这篇文章主要介绍了Pandas读取并修改excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-02-02
  • Python实现批量压缩图片

    Python实现批量压缩图片

    这篇文章主要为大家详细介绍了Python实现批量压缩图片的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • Python的“二维”字典 (two-dimension dictionary)定义与实现方法

    Python的“二维”字典 (two-dimension dictionary)定义与实现方法

    这篇文章主要介绍了Python的“二维”字典 (two-dimension dictionary)定义与实现方法,结合实例形式分析了Python模拟实现类似二维数组形式的二维字典功能,需要的朋友可以参考下
    2016-04-04
  • python快速建立超简单的web服务器的实现方法

    python快速建立超简单的web服务器的实现方法

    某些条件测试,需要一个简单的web服务器测试一下,为此专门去配置个nginx 或者 apache服务器略显麻烦,这里就为大家介绍一下使用python快速建立超简单的web服务器的方法,需要的朋友可以参考下
    2018-02-02
  • 说一说Python logging

    说一说Python logging

    这篇文章主要和大家聊一聊Python logging,Python logging是什么,Python logging的作用是什么,感兴趣的小伙伴们可以参考一下
    2016-04-04
  • python常见的格式化输出小结

    python常见的格式化输出小结

    今天在写代码的时候,需要统一化输出格式进行,一时想不起竟具体细节,用了最笨的方法,所以觉得有必要将常见的方法进行一个总结。下面这篇文中就给大家总结了python中常见的格式化输出,比如打印字符串、打印整数和打印浮点数等,下面来看看详细的输出方法吧。
    2016-12-12
  • Python中的ceil()方法使用教程

    Python中的ceil()方法使用教程

    这篇文章主要介绍了Python中的ceil()方法使用教程,是Python入门中必会的方法之一,需要的朋友可以参考下
    2015-05-05
  • Python-jenkins模块之folder相关操作介绍

    Python-jenkins模块之folder相关操作介绍

    这篇文章主要介绍了Python-jenkins模块之folder相关操作介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-05-05
  • python批量导入数据进Elasticsearch的实例

    python批量导入数据进Elasticsearch的实例

    今天小编就为大家分享一篇python批量导入数据进Elasticsearch的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05

最新评论