一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - Python中利用函數裝飾器實現備忘功能

Python中利用函數裝飾器實現備忘功能

2020-05-25 09:53python-course 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")

 

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 午夜欧美精品久久久久久久 | 波多野结衣快播 | 色吧| 男人久久天堂 | 免费一级生活片 | 国产午夜亚洲精品 | 天堂网在线.www天堂在线视频 | 免费一级毛片在线播放放视频 | 精品久久洲久久久久护士免费 | 福利色播 | 国产高清露脸学生在线观看 | 国产极品精频在线观看 | 国产欧美精品一区二区三区 | 女医学护士一级毛片 | 亚裔aⅴ艳星katsuni | 羞羞视频污 | 草莓秋葵菠萝蜜绿巨人污 | 无码一区国产欧美在线资源 | 国产精品久久久久久搜索 | 思思玖玖玖在线精品视频 | 高h折磨调教古代 | 成人另类视频 | 国产自拍专区 | 亚洲精品福利一区二区在线观看 | chinesegay黑袜玩奴 | 激情偷拍网 | 小舞同人18av黄漫网站 | 欧美人与日本人xx在线视频 | 美女秘密网站 | 欧美在线一二三区 | 国产欧美又粗又猛又爽老 | 4444www免费看| 免费视频左左视频 | 小便japanesewctv| 妹妹骑上来蹭着蹭着就射了 | 息与子中文字幕完整在线 | 亚洲精品国产精品麻豆99 | 亚洲偷窥图区色 | 嗯好爽视频 | 天美视频在线 | 国产一区二区三区水野朝阳 |