Python實(shí)現(xiàn)緩存的兩個(gè)簡(jiǎn)單方法
緩存是一種用于提高應(yīng)用程序性能的技術(shù),它通過(guò)臨時(shí)存儲(chǔ)程序獲得的結(jié)果,以便在以后需要時(shí)重用它們。
在本文中,我們將學(xué)習(xí)Python中的不同緩存技術(shù),包括functools模塊中的@ lru_cache和@ cache裝飾器。
簡(jiǎn)單示例: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é)問(wèn)題,花一個(gè)小時(shí)得到正確的答案。如果第二天我們必須解決同樣的問(wèn)題,那么重用我們以前的工作而不是從頭開(kāi)始會(huì)很有幫助。
Python中的緩存遵循類似的原則–它在函數(shù)調(diào)用中計(jì)算值時(shí)存儲(chǔ)這些值,以便在再次需要時(shí)重用它們。這種類型的緩存也稱為記憶化。
讓我們看一個(gè)簡(jiǎn)短的例子,它計(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í)間)。
但是,我們可以使用緩存來(lái)避免多次計(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ù)。讓我們通過(guò)一個(gè)示例來(lái)探索functools.lru_cache()和functools.cache()。
讓我們編寫(xiě)一個(gè)函數(shù)sum_digits(),它接受一個(gè)數(shù)字序列并返回這些數(shù)字的位數(shù)之和。例如,如果我們使用元組(23,43,8)作為輸入,那么:
- 23的數(shù)字之和是5
- 43的數(shù)字之和是7
- 8的數(shù)字之和是8
- 因此,總和為20。
這是我們可以編寫(xiě)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ù)來(lái)探索創(chuàng)建緩存的不同方法。
Python手動(dòng)緩存
讓我們首先手動(dòng)創(chuàng)建該高速緩存。雖然我們也可以很容易地自動(dòng)化,但手動(dòng)創(chuàng)建緩存有助于我們理解這個(gè)過(guò)程。
讓我們創(chuàng)建一個(gè)字典,并在每次使用新值調(diào)用函數(shù)時(shí)添加鍵值對(duì)來(lái)存儲(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ì)地解釋上面的代碼。首先,請(qǐng)注意,我們?cè)诙x函數(shù)后創(chuàng)建了字典sum_digits.my_cache,即使我們?cè)诤瘮?shù)定義中使用了它。
函數(shù)的作用是:檢查傳遞給函數(shù)的參數(shù)是否已經(jīng)是sum_digits.my_cache字典中的鍵之一。僅當(dāng)參數(shù)不在該高速緩存中時(shí),才計(jì)算所有數(shù)字的和。
由于我們?cè)谡{(diào)用函數(shù)時(shí)使用的參數(shù)作為字典中的鍵,因此它必須是可散列數(shù)據(jù)類型。列表是不可散列的,所以我們不能將它用作字典中的鍵。例如,讓我們嘗試用列表而不是元組來(lái)替換數(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版開(kāi)始就有了lru_cache()裝飾器。函數(shù)名開(kāi)頭的“lru”代表“least recently used”。我們可以把緩存看作是一個(gè)用來(lái)存儲(chǔ)經(jīng)常使用的東西的盒子–當(dāng)它填滿時(shí),LRU策略會(huì)扔掉我們很長(zhǎng)時(shí)間沒(méi)有使用過(guò)的東西,為新的東西騰出空間。
讓我們用@functools.lru_cache來(lái)裝飾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è)置不同的最大緩存大小:
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è)更簡(jiǎn)單、更快速的緩存裝飾器——functools. cache()。這個(gè)裝飾器有兩個(gè)主要特點(diǎn):
- 它沒(méi)有最大大小-這類似于調(diào)用functools.lru_cache(maxsize=None)。
- 它存儲(chǔ)所有函數(shù)調(diào)用及其結(jié)果(它不使用LRU策略)。這適用于輸出相對(duì)較小的函數(shù),或者當(dāng)我們不需要擔(dān)心緩存大小限制時(shí)。
讓我們?cè)趕um_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)
請(qǐng)注意,我們也可以使用不同的導(dǎo)入方式:
from functools import cache
這樣,我們就可以只使用@cache來(lái)裝飾我們的函數(shù)。
其他緩存策略
Python自己的工具實(shí)現(xiàn)了LRU緩存策略,刪除最近最少使用的條目,為新值騰出空間。
讓我們來(lái)看看其他一些緩存策略:
- 先進(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)。想象一下自助餐廳里的一堆盤(pán)子。我們最近放入堆棧的盤(pán)子(最后一個(gè))是我們將首先取出的盤(pán)子(第一個(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)常被訪問(wèn)(LRU會(huì)保留它),但一周后,新聞可能會(huì)過(guò)時(shí)。
Python中緩存時(shí)的常見(jiàn)挑戰(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)存,如果緩存無(wú)限增長(zhǎng),這可能會(huì)導(dǎo)致性能問(wèn)題。
- 復(fù)雜性:添加緩存會(huì)在創(chuàng)建和維護(hù)該高速緩存時(shí)給系統(tǒng)帶來(lái)復(fù)雜性。通常,好處大于這些成本,但這種增加的復(fù)雜性可能會(huì)導(dǎo)致難以發(fā)現(xiàn)和糾正的錯(cuò)誤。
結(jié)論
當(dāng)對(duì)同一數(shù)據(jù)重復(fù)執(zhí)行計(jì)算密集型操作時(shí),我們可以使用緩存來(lái)優(yōu)化性能。
Python有兩個(gè)裝飾器在調(diào)用函數(shù)時(shí)創(chuàng)建緩存:functools模塊中的@lru_cache和@cache。
但是,我們需要確保該高速緩存保持最新,并正確管理內(nèi)存。
到此這篇關(guān)于Python實(shí)現(xiàn)緩存的兩個(gè)簡(jiǎn)單方法的文章就介紹到這了,更多相關(guān)Python緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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à)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12
python和websocket構(gòu)建實(shí)時(shí)日志跟蹤器的步驟
這篇文章主要介紹了python和websocket構(gòu)建實(shí)時(shí)日志跟蹤器的步驟,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-04-04
Python控制臺(tái)輸出時(shí)刷新當(dāng)前行內(nèi)容而不是輸出新行的實(shí)現(xiàn)
今天小編就為大家分享一篇Python控制臺(tái)輸出時(shí)刷新當(dāng)前行內(nèi)容而不是輸出新行的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02
python opencv檢測(cè)直線 cv2.HoughLinesP的實(shí)現(xiàn)
cv2.HoughLines()函數(shù)是在二值圖像中查找直線,本文結(jié)合示例詳細(xì)的介紹了cv2.HoughLinesP的用法,感興趣的可以了解一下2021-06-06
Selenium自動(dòng)化測(cè)試實(shí)現(xiàn)窗口切換
這篇文章主要介紹了Selenium自動(dòng)化測(cè)試實(shí)現(xiàn)窗口切換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
python?pygame英雄循環(huán)飛行及作業(yè)示例
這篇文章主要為大家介紹了python?pygame英雄循環(huán)飛行及作業(yè)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

