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