最近,我讀了一篇有趣的文章,文中介紹了一些未充分使用的Python特性的。在文章中,作者提到,從Python 3.2開始,標准庫附帶了一個內置的裝飾器functools.lru_cache
。我發現這個裝飾器很令人興奮,有了它,我們有可能輕松地為許多應用程序加速。
你可能在想,這很好,但這個裝飾器究竟是什么?它提供對已構建的緩存的訪問,該緩存使用LRU(譯者注: Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。)的置換策略,因此被命名為lru_cache
。當然,這句話聽起來可能有點令人膽怯,所以讓我們把它分解一下。

這里也要注意:不管你是為了Python就業還是興趣愛好,記住:項目開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,里面很多新python教程項目,還可以跟老司機交流討教!
什么是緩存?
緩存是一個可以快速訪問的地方,可以在它里面存儲訪問速度較慢的內容。為了演示這一點,讓我們以你的web瀏覽器為例。

從網絡上讀取網頁可能需要幾秒鍾,即使是快速的網絡連接也如此。在計算機時代,這個問題是永恆的。為了解決這個問題,瀏覽器將你已經訪問過的網頁存儲在計算機的緩存中,這樣訪問速度會加快數千倍。
使用緩存下載網頁的步驟如下:
- 檢查頁面的本地緩存。如果頁面在那里,返回該頁面。
- 在因網上找到網頁並從那里下載。
- 將該網頁存儲在緩存中,以便將來更快地訪問。
雖然緩存並不會讓你第一次訪問網頁的速度加快,但通常你是要屢次訪問某一個網站頁面的(想想Facebook——注:對多數國人來講,可能不是這個網站,或者你的電子郵件),有了緩存之后,以后每次訪問都會更快。
瀏覽器並不是唯一使用緩存的,從服務器到CPU和硬盤或SSD之間的計算機硬件,它們無處不在。從緩存中可以很快地獲取數據,因此當你不止一次獲取數據時,它可以大大加快程序的速度。
LRU是什么意思?
緩存只能存儲有限數量的東西,而且通常它比可能存入所緩存的東西要小得多(例如,你的硬盤比互聯網小得多)。這意味着有時需要將緩存中已有內容替換掉,放入其他內容。對於去掉什么的決策方法被稱為置換策略。
這就是LRU的用武之地。LRU代表最近用得最少的緩存中內容,這是一種常用的緩存置換策略。
為什么置換策略很重要?
“最近使用最少”這種置換策略的基本思想是:如果你有一段時間沒有訪問過某個東西,你可能近期不會訪問它。要使用此策略,只需在緩存已滿時刪除最早使用的項即可。

在上圖中,緩存中的每個項都附帶了訪問時間。依據LRU策略,選擇訪問時間為2:55PM 的項作為要置換的項,因為它是最早被訪問的。如果有兩個對象具有相同的訪問時間,那么LRU將從中隨機選擇一個。
這種去掉長時間不用的東西的策略,被稱為Bélády的最優算法,它是置換緩存內容的最佳策略。當然,我們根本不知道未來會有什么操作。謝天謝地,在許多情況下,LRU提供了近乎最佳的性能。

怎樣使用它?
functools.lru_cache
是一個裝飾器,因此你可以將它放在函數的頂部:
import functools
@functools.lru_cache(maxsize=128)
def fib(n):
if n < 2: return 1 return fib(n-1) + fib(n-2) 復制代碼
Fibonacci數列在遞歸示例中經常被用到,要提升這個函數的速度,使用functools.lru_cache
之后,不費吹灰之力,就能讓這個遞歸函數狂飆。在我的機器上運行這些代碼,得到了這個函數有緩存版本和沒有緩存版本的以下結果。
$ python3 -m timeit -s 'from fib_test import fib' 'fib(30)' 10 loops, best of 3: 282 msec per loop $ python3 -m timeit -s 'from fib_test import fib_cache' 'fib_cache(30)' 10000000 loops, best of 3: 0.0791 usec per loop 復制代碼
增加一行代碼之后,速度提高了3565107倍。
當然,我認為很難看出你在實際中會如何使用它,因為我們很少需要計算斐波那契數列。回到web頁面示例,我們可以舉一個更實際的用緩存渲染前端模板的例子。
在服務器開發中,通常單個頁面存儲為具有占位符變量的模板。例如,下面是一個頁面模板,該頁面顯示某一天各種足球比賽的結果。
<html>
<body>
<h1>Matches for {{day}}</h1> <table id="matches"> <tr> <td>Home team</td> <td>Away team</td> <td>Score</td> </tr> {% for match in matches %} <tr> <td>{{match["home"]}}</td> <td>{{match["away"]}}</td> <td>{{match["home_goals"]}} - {{match["away_goals"]}}</td> </tr> {% endfor %} </table> </body> </html> 復制代碼
呈現模板時,看起來如下所示:

這是緩存的主要目標,因為每天的結果不會改變,而且很可能每天會有多次訪問。下面是一個提供此模板的Flask應用程序。我引入了50ms的延遲來模擬通過網絡或者從大型數據庫獲取匹配字典。
import json
import time
from flask import Flask, render_template
app = Flask(__name__)
with open('match.json','r') as f: match_dict = json.load(f) def get_matches(day): # simulate network/database delay time.sleep(0.05) return match_dict[day] @app.route('/matches/<day>') def show_matches(day): matches = get_matches(day) return render_template('matches.html', matches=matches, day=day) if __name__ == "__main__": app.run() 復制代碼
使用requests
在不緩存的情況下獲得三天的數據,在我的計算機上本地運行平均需要171ms。這還不錯,但我們可以做得更好,即使考慮到人為的延遲。
@app.route('/matches/<day>') @functools.lru_cache(maxsize=4) def show_matches(day): matches = get_matches(day) return render_template('matches.html', matches=matches, day=day) 復制代碼
在本例中,我設置了maxsize=4
,因為我的測試腳本只有相同的三天,最好設置2次冪。使用這種方法,10個循環的平均速度可以降到13.7ms。

還有什么應該知道?
Python文檔雖然很詳細,但是有一些東西還是要強調的。
內置函數
裝飾器附帶了一些很有用的內置函數。cache_info()
返回訪問數(hits)、未訪問數(misses)和當前緩存使用量(currsize)、最大容量(maxsize),幫助你了解緩存使用情況。cache_clear()
將刪除緩存中的所有元素。
有時候不要使用緩存
通常,只有在以下情況下才能使用緩存:
- 在緩存期內,數據不會更改。
- 函數將始終為相同的參數返回相同的值(因此時間和隨機對緩存沒有意義)。
- 函數沒有副作用。如果緩存被訪問,則永遠不會調用該函數,因此請確保不更改其中的任何狀態。
- 函數不返回不同的可變對象。例如,返回列表的函數不適合緩存,因為將要緩存的是對列表的引用,而不是列表內容。
最后注意:不管你是為了Python就業還是興趣愛好,記住:項目開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,里面很多新python教程項目,還可以跟老司機交流討教!本文的文字及圖片來源於網絡加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理。