Python實(shí)現(xiàn)緩存的兩個(gè)簡單方法
緩存是一種用于提高應(yīng)用程序性能的技術(shù),它通過臨時(shí)存儲(chǔ)程序獲得的結(jié)果,以便在以后需要時(shí)重用它們。
在本文中,我們將學(xué)習(xí)Python中的不同緩存技術(shù),包括functools模塊中的@ lru_cache和@ cache裝飾器。
簡單示例:Python緩存實(shí)現(xiàn)
要在Python中創(chuàng)建緩存,我們可以使用functools模塊中的@cache裝飾器。在下面的代碼中,注意print()函數(shù)只執(zhí)行一次:
import functools @functools.cache def square(n): print(f"Calculating square of {n}") return n * n # Testing the cached function print(square(4)) # Output: Calculating square of 4 \n 16 print(square(4)) # Output: 16 (cached result, no recalculation)
輸出
Calculating square of 4
16
16
Python中的緩存是什么
假設(shè)我們需要解決一個(gè)數(shù)學(xué)問題,花一個(gè)小時(shí)得到正確的答案。如果第二天我們必須解決同樣的問題,那么重用我們以前的工作而不是從頭開始會(huì)很有幫助。
Python中的緩存遵循類似的原則–它在函數(shù)調(diào)用中計(jì)算值時(shí)存儲(chǔ)這些值,以便在再次需要時(shí)重用它們。這種類型的緩存也稱為記憶化。
讓我們看一個(gè)簡短的例子,它計(jì)算了兩次大范圍的數(shù)字之和:
output = sum(range(100_000_001)) print(output) output = sum(range(100_000_001)) print(output)
輸出
5000000050000000
5000000050000000
計(jì)算兩次運(yùn)行時(shí)間:
import timeit print( timeit.timeit( "sum(range(100_000_001))", globals=globals(), number=1, ) ) print( timeit.timeit( "sum(range(100_000_001))", globals=globals(), number=1, ) )
輸出
1.2157779589979327
1.1848394999979064
輸出顯示,兩個(gè)調(diào)用所花費(fèi)的時(shí)間大致相同(取決于我們的設(shè)置,我們可能會(huì)獲得更快或更慢的執(zhí)行時(shí)間)。
但是,我們可以使用緩存來避免多次計(jì)算相同的值。我們可以使用內(nèi)置functools模塊重新定義:
import functools import timeit sum = functools.cache(sum) print( timeit.timeit( "sum(range(100_000_001))", globals=globals(), number=1, ) ) print( timeit.timeit( "sum(range(100_000_001))", globals=globals(), number=1, ) )
輸出
1.2760689580027247
2.3330067051574588e-06
第二個(gè)調(diào)用現(xiàn)在需要幾微秒的時(shí)間,而不是一秒鐘,因?yàn)閺?到100,000,000的數(shù)字之和的結(jié)果已經(jīng)計(jì)算并緩存了-第二個(gè)調(diào)用使用之前計(jì)算和存儲(chǔ)的值。
functools.cache()裝飾器是在Python 3.9版本中添加的,但我們可以在舊版本中使用functools.lru_cache()。
Python緩存:不同的方法
Python的functools模塊有兩個(gè)裝飾器用于將緩存應(yīng)用于函數(shù)。讓我們通過一個(gè)示例來探索functools.lru_cache()和functools.cache()。
讓我們編寫一個(gè)函數(shù)sum_digits(),它接受一個(gè)數(shù)字序列并返回這些數(shù)字的位數(shù)之和。例如,如果我們使用元組(23,43,8)作為輸入,那么:
- 23的數(shù)字之和是5
- 43的數(shù)字之和是7
- 8的數(shù)字之和是8
- 因此,總和為20。
這是我們可以編寫sum_digits函數(shù)的一種方式:
def sum_digits(numbers): return sum( int(digit) for number in numbers for digit in str(number) ) numbers = 23, 43, 8 print(sum_digits(numbers))
輸出
20
讓我們使用這個(gè)函數(shù)來探索創(chuàng)建緩存的不同方法。
Python手動(dòng)緩存
讓我們首先手動(dòng)創(chuàng)建該高速緩存。雖然我們也可以很容易地自動(dòng)化,但手動(dòng)創(chuàng)建緩存有助于我們理解這個(gè)過程。
讓我們創(chuàng)建一個(gè)字典,并在每次使用新值調(diào)用函數(shù)時(shí)添加鍵值對來存儲(chǔ)結(jié)果。如果我們用一個(gè)已經(jīng)存儲(chǔ)在這個(gè)字典中的值調(diào)用函數(shù),函數(shù)將返回存儲(chǔ)的值,而不會(huì)再次計(jì)算:
import random import timeit def sum_digits(numbers): if numbers not in sum_digits.my_cache: sum_digits.my_cache[numbers] = sum( int(digit) for number in numbers for digit in str(number) ) return sum_digits.my_cache[numbers] sum_digits.my_cache = {} numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000)) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) ) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) )
輸出
0.28875587500078836
0.0044607500021811575
第二次調(diào)用sum_digits(numbers)比第一次調(diào)用快得多,因?yàn)樗褂昧司彺娴闹怠?/p>
現(xiàn)在讓我們更詳細(xì)地解釋上面的代碼。首先,請注意,我們在定義函數(shù)后創(chuàng)建了字典sum_digits.my_cache,即使我們在函數(shù)定義中使用了它。
函數(shù)的作用是:檢查傳遞給函數(shù)的參數(shù)是否已經(jīng)是sum_digits.my_cache字典中的鍵之一。僅當(dāng)參數(shù)不在該高速緩存中時(shí),才計(jì)算所有數(shù)字的和。
由于我們在調(diào)用函數(shù)時(shí)使用的參數(shù)作為字典中的鍵,因此它必須是可散列數(shù)據(jù)類型。列表是不可散列的,所以我們不能將它用作字典中的鍵。例如,讓我們嘗試用列表而不是元組來替換數(shù)字-這將引發(fā)TypeError:
# ... numbers = [random.randint(1, 1000) for _ in range(1_000_000)] # ...
輸出
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
手動(dòng)創(chuàng)建緩存非常適合學(xué)習(xí),但現(xiàn)在讓我們探索更快的方法。
使用functools.lru_cache()進(jìn)行Python緩存
Python從3.2版開始就有了lru_cache()裝飾器。函數(shù)名開頭的“lru”代表“least recently used”。我們可以把緩存看作是一個(gè)用來存儲(chǔ)經(jīng)常使用的東西的盒子–當(dāng)它填滿時(shí),LRU策略會(huì)扔掉我們很長時(shí)間沒有使用過的東西,為新的東西騰出空間。
讓我們用@functools.lru_cache來裝飾sum_digits函數(shù):
import functools import random import timeit @functools.lru_cache def sum_digits(numbers): return sum( int(digit) for number in numbers for digit in str(number) ) numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000)) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) ) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) )
輸出
0.28326129099878017
0.002184917000704445
多虧了緩存,第二個(gè)調(diào)用的運(yùn)行時(shí)間大大縮短。
默認(rèn)情況下,該高速緩存存儲(chǔ)計(jì)算的前128個(gè)值。一旦所有128個(gè)位置都滿了,算法就會(huì)刪除最近最少使用的(LRU)值,為新值騰出空間。
當(dāng)我們使用maxsize參數(shù)修飾函數(shù)時(shí),我們可以設(shè)置不同的最大緩存大?。?/p>
import functools import random import timeit @functools.lru_cache(maxsize=5) def sum_digits(numbers): return sum( int(digit) for number in numbers for digit in str(number) ) # ...
在這種情況下,該高速緩存僅存儲(chǔ)5個(gè)值。如果我們不想限制該高速緩存的大小,也可以將maxsize參數(shù)設(shè)置為None。
使用functools.cache()進(jìn)行Python緩存
Python 3.9包含了一個(gè)更簡單、更快速的緩存裝飾器——functools. cache()。這個(gè)裝飾器有兩個(gè)主要特點(diǎn):
- 它沒有最大大小-這類似于調(diào)用functools.lru_cache(maxsize=None)。
- 它存儲(chǔ)所有函數(shù)調(diào)用及其結(jié)果(它不使用LRU策略)。這適用于輸出相對較小的函數(shù),或者當(dāng)我們不需要擔(dān)心緩存大小限制時(shí)。
讓我們在sum_digits函數(shù)上使用@functools.cache裝飾器:
import functools import random import timeit @functools.cache def sum_digits(numbers): return sum( int(digit) for number in numbers for digit in str(number) ) numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000)) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) ) print( timeit.timeit( "sum_digits(numbers)", globals=globals(), number=1 ) )
輸出
0.16661812500024098
0.0018135829996026587
使用@functools.cache修飾sum_digits()等效于將sum_digits賦值給functools.cache():
# ... def sum_digits(numbers): return sum( int(digit) for number in numbers for digit in str(number) ) sum_digits = functools.cache(sum_digits)
請注意,我們也可以使用不同的導(dǎo)入方式:
from functools import cache
這樣,我們就可以只使用@cache來裝飾我們的函數(shù)。
其他緩存策略
Python自己的工具實(shí)現(xiàn)了LRU緩存策略,刪除最近最少使用的條目,為新值騰出空間。
讓我們來看看其他一些緩存策略:
- 先進(jìn)先出(FIFO):當(dāng)該高速緩存已滿時(shí),刪除添加的第一個(gè)項(xiàng),為新值騰出空間。LRU和FIFO之間的區(qū)別在于,LRU將最近使用的項(xiàng)保存在該高速緩存中,而FIFO丟棄最舊的項(xiàng)而不管是否使用。
- 后進(jìn)先出(LIFO):當(dāng)該高速緩存已滿時(shí),刪除最近添加的項(xiàng)。想象一下自助餐廳里的一堆盤子。我們最近放入堆棧的盤子(最后一個(gè))是我們將首先取出的盤子(第一個(gè))。
- Most-recently used(MRU):當(dāng)該高速緩存中需要空間時(shí),將丟棄最近使用的值。
- 隨機(jī)替換(RR):該策略隨機(jī)丟棄一個(gè)項(xiàng)目,為新項(xiàng)目騰出空間。
這些策略還可以與有效生存期的度量相結(jié)合,有效生存期指的是該高速緩存中的一段數(shù)據(jù)被認(rèn)為有效或相關(guān)的時(shí)間。想象一下緩存中的一篇新聞文章。它可能經(jīng)常被訪問(LRU會(huì)保留它),但一周后,新聞可能會(huì)過時(shí)。
Python中緩存時(shí)的常見挑戰(zhàn)
我們已經(jīng)了解了Python中緩存的優(yōu)點(diǎn)。在實(shí)現(xiàn)緩存時(shí),還需要記住一些挑戰(zhàn)和缺點(diǎn):
- 緩存失效和一致性:數(shù)據(jù)可能會(huì)隨著時(shí)間而變化。因此,存儲(chǔ)在緩存中的值也可能需要更新或刪除。
- 內(nèi)存管理:在緩存中存儲(chǔ)大量數(shù)據(jù)需要內(nèi)存,如果緩存無限增長,這可能會(huì)導(dǎo)致性能問題。
- 復(fù)雜性:添加緩存會(huì)在創(chuàng)建和維護(hù)該高速緩存時(shí)給系統(tǒng)帶來復(fù)雜性。通常,好處大于這些成本,但這種增加的復(fù)雜性可能會(huì)導(dǎo)致難以發(fā)現(xiàn)和糾正的錯(cuò)誤。
結(jié)論
當(dāng)對同一數(shù)據(jù)重復(fù)執(zhí)行計(jì)算密集型操作時(shí),我們可以使用緩存來優(yōu)化性能。
Python有兩個(gè)裝飾器在調(diào)用函數(shù)時(shí)創(chuàng)建緩存:functools模塊中的@lru_cache和@cache。
但是,我們需要確保該高速緩存保持最新,并正確管理內(nèi)存。
到此這篇關(guān)于Python實(shí)現(xiàn)緩存的兩個(gè)簡單方法的文章就介紹到這了,更多相關(guān)Python緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 實(shí)現(xiàn)檢驗(yàn)33品種數(shù)據(jù)是否是正態(tài)分布
今天小編就為大家分享一篇python 實(shí)現(xiàn)檢驗(yàn)33品種數(shù)據(jù)是否是正態(tài)分布,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12python和websocket構(gòu)建實(shí)時(shí)日志跟蹤器的步驟
這篇文章主要介紹了python和websocket構(gòu)建實(shí)時(shí)日志跟蹤器的步驟,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-04-04Python控制臺(tái)輸出時(shí)刷新當(dāng)前行內(nèi)容而不是輸出新行的實(shí)現(xiàn)
今天小編就為大家分享一篇Python控制臺(tái)輸出時(shí)刷新當(dāng)前行內(nèi)容而不是輸出新行的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-02-02python opencv檢測直線 cv2.HoughLinesP的實(shí)現(xiàn)
cv2.HoughLines()函數(shù)是在二值圖像中查找直線,本文結(jié)合示例詳細(xì)的介紹了cv2.HoughLinesP的用法,感興趣的可以了解一下2021-06-06Selenium自動(dòng)化測試實(shí)現(xiàn)窗口切換
這篇文章主要介紹了Selenium自動(dòng)化測試實(shí)現(xiàn)窗口切換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03python?pygame英雄循環(huán)飛行及作業(yè)示例
這篇文章主要為大家介紹了python?pygame英雄循環(huán)飛行及作業(yè)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08