Python 中怎麼來實現類似 Cache 的功能?
作者:崔慶才
近期要實現一個小的功能:我需要在短期內對某些數據進行快速查詢、修改等操作,但這些數據僅僅在短期內會用到,過一小段時間就可以銷燬了。
所以,爲了提高數據的操作效率,放在內存中無疑是非常合適的,但是內存總是有限的,總不能無限地放吧,內存溢出了咋辦?
所以,有沒有一種自動回收機制,可以過一小段時間自動將舊的數據進行移除或替換呢?
仔細一想,這不就相當於一個 Cache 嗎?既能在短期內實現快速查詢、修改等操作,等不用了就被自動置換掉。
是的,沒錯,那在 Python 中有沒有類似實現呢?
有的,叫做 cachetools,這裏我們就來簡單介紹下它的用法。
介紹
cachetools,這是一個可擴展的基於內存的 Collections、Decorators 的封裝實現。
因爲是 Cache,那麼就一定有它的頁面置換算法。根據操作系統學過的一些知識,置換算法就會有 LRU、LFU、FIFO 等等。比如說,當 Cache 已經滿了的情況下,如果這時候再插入一個新的數據,那麼這時候就需要根據頁面置換算法對已有的數據進行置換,用新的數據替代舊的數據,保證 Cache 最大佔用量不會超標。
廢話不多說了,這裏我們來體驗下這個庫的具體用法吧。
首先是安裝,直接使用 pip3 安裝即可:
pip3 install cachetools
安裝好之後,我們再來看看它的具體用法。
基本 Cache 的使用
我們來看一個簡單的實例:
from cachetools import Cache
cache = Cache(maxsize=3)
cache['1'] = 'Hello'
cache['2'] = 'World'
print('current size', cache.currsize)
cache.pop('2')
print(cache.items)
print('length', len(cache))
cache['3'] = 'Hello'
cache['4'] = 'World'
print('current size', cache.currsize)
cache['5'] = 'Hello'
print('current size', cache.currsize)
print(cache.items)
運行結果如下:
current size 2
<bound method Mapping.items of Cache([('1', 'Hello')], maxsize=3, currsize=1)>
length 1
current size 3
current size 3
<bound method Mapping.items of Cache([('3', 'Hello'), ('4', 'World'), ('5', 'Hello')], maxsize=3, currsize=3)>
首先這裏聲明瞭一個 Cache 對象,有一個必傳的參數是 maxsize,這裏設置爲 3,這裏的 3 其實就是長度的意思,並不是實際內存佔用大小。
接着我們賦值了 1 和 2 兩個鍵名,接着打印出來了當前 Cache 的大小,所以結果就是 2,這個 size 就是一個單純的數量值。
然後接着調用了 pop 方法移除了 2 對應的內容,然後打印 Cache 的所有內容和對應長度,理所應當,長度就是 2,然後就剩下一個值。
接着我們又賦值了 3 和 4 兩個鍵名,然後打印了當前 Cache 的大小,這會 Cache 達到了 maxsize,結果就是 3。
最後我們又賦值了 5 這個鍵名,然後打印了當前 Cache 的大小和 Cache 的所有內容,因爲 Cache 已經達到了 maxsize 了,所以結果依然是 3,最前面的 1 這個鍵名對應的內容就被移除了。
所以,這個 Cache 對象可以維持一個最大恆定大小,並且保證長度不會超過 maxsize。
其他 Cache 的使用
當然除了 Cache,還有一些 Cache 的子類,比如說 FIFOCache、LFUCahce、LRUCache、MRUCache、RRCache,這裏簡單說下:
-
LFU:Least Frequently Used,就是淘汰最不常用的。
-
LRU:Least Recently Used,就是淘汰最久不用的。
-
MRU:Most Recently Used,與 LRU 相反,淘汰最近用的。
-
RR:Random Replacement,就是隨機替換。
具體的實例這裏就不再講解了。
特殊 TTLCache 的使用
當然除了基本的 Cache,cachetools 還提供了一種特殊的 Cache 實現,叫做 TTLCache。
TTL 就是 time-to-live 的簡稱,也就是說,Cache 中的每個元素都是有過期時間的,如果超過了這個時間,那這個元素就會被自動銷燬。如果都沒過期並且 Cache 已經滿了的話,那就會採用 LRU 置換算法來替換掉最久不用的,以此來保證數量。
下面我們來看一個樣例:
from datetime import timedelta, datetime
from cachetools import TTLCache
from time import sleep
cache = TTLCache(maxsize=3, ttl=timedelta(seconds=5), timer=datetime.now)
cache['1'] = 'Hello'
sleep(1)
cache['2'] = 'World'
print(cache.items)
sleep(4.5)
print(cache.items)
sleep(1)
print(cache.items)
運行結果如下:
<bound method Mapping.items of TTLCache([('1', 'Hello'), ('2', 'World')], maxsize=3, currsize=2)>
<bound method Mapping.items of TTLCache([('2', 'World')], maxsize=3, currsize=1)>
<bound method Mapping.items of TTLCache([], maxsize=3, currsize=0)>
這裏我們聲明瞭一個 TTLCache,maxsize 是 3,然後 ttl 設置爲了 5 秒,也就是說,每個元素 5 秒之後都會過期。
首先我們賦值 1 這個鍵名爲 Hello,然後 1 秒之後賦值 2 這個鍵名爲 World,接着將現有 Cache 的結果輸出出來。
接着等待 4.5 秒,這時候 1 這個鍵名就已經超過 5 秒了,所以 1 這個鍵名理應就被銷燬了。
接着再等待 1 秒,這時候 2 這個鍵名也超過 5 秒了,所以 2 這個鍵名也理應就被銷燬了。
最後看運行結果也如我們期望的一樣。
大小計算
有的同學說,你這裏 maxsize 用的這個數字指的是內容的長度,但實際上不同的內容佔用的空間是完全不一樣的,有沒有根據實際內存佔用來計算 size 的方法呢?
有的!
這裏我們只需要替換掉 Cache 的 getsizeof 方法即可。
這裏我們需要額外引入一個庫,叫做 pympler,它提供了一個 asizeof 方法可以計算實際 Object 的佔用內存大小,單位是 bytes。
pympler 安裝:
pip3 install pympler
所以,如果我們要設置 Cache 佔用的最大內存大小,比如 2MB,那就可以這麼設置:
from cachetools import Cache
from pympler import asizeof
cache = Cache(maxsize=2 * 1024 * 1024, getsizeof=asizeof.asizeof)
cache['a'] = '123'
print(cache.currsize)
cache['b'] = '123'
print(cache.currsize)
cache['c'] = '456'
print(cache.currsize)
cache['d'] = {
'a': 'b',
'b': 'c',
'c': 'd'
}
print(cache.currsize)
這裏 maxsize 我們就設置爲了 2MB,同時 getsizeof 方法設置爲了 pympler 的 asizeof 方法,這樣 Cache 在計算 size 的時候就會用 asizeof 方法了。
這裏我們隨便插入一些數據,看看實際的 size 變化,運行結果如下:
56
112
168
640
其結果就是 Cache 佔用的字節數。可以看到數據的複雜度高,佔用的空間越大。
更多
好了,其實到現在爲止,基本的 Cache 和 TTLCache 就夠我們使用了。
另外 cachetools 還提供了一些裝飾器,可以幫助我們更方便地使用 Cache,更多內容可以看官方文檔:https://cachetools.readthedocs.io/en/stable/。
看完記得關注 @進擊的 Coder
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/JELB0-xyS4c7oErypG4hLg