Pythonで有効期限(TTL)付きキャッシュを実装する (3)
シリーズ - Pythonで有効期限(TTL)付きキャッシュを実装する
コンテンツ
今回は、前回までに実装したTTLキャッシュデコレータに対し、
キャッシュのオン・オフを切り替える機能を追加します。
キャッシュオン・オフ切り替え機能の概要
今回追加するキャッシュ切り替え機能の仕様は以下のとおりです:
lru_cache
デコレータが付けられた関数にはuse_cache
引数が追加される。
この引数でキャッシュのオンオフを切り替えられるようになる。use_cache=False
と指定するとキャッシュを無視して実行される。
計算結果はキャッシュに保存される。- 再度
use_cache=True
とすると、キャッシュの内容が利用される。
実装方法
実装方法は色々と考えられそうですが、今回は以下のように行いました。
- デコレータの入力関数に
cache_offset
というカウンターを属性として追加し、
use_cache=False
で関数が実行された際にカウントアップさせ、ハッシュ値に加算する。
以下コードのハイライト部分が、今回の変更箇所です。
def ttl_cache(ttl_seconds=3600, use_cache=True):
"""有効期限(TTL)付きキャッシュ機能を持つデコレータ.
関数に use_cache 引数を追加し、キャッシュのオン・オフを切り替えることができる.
Args:
ttl_seconds (int or None, optional): 有効期限(秒). Defaults to 3600.
use_cache (bool, optional): デフォルトでキャッシュを有効化するかどうか. Defaults to True.
"""
def ttl_cache_deco(func):
"""関数を入力として、それに有効期限付きキャッシュ機能を実装した関数を返す。"""
# キャッシュ機能とダミー引数を追加した関数を作成
@lru_cache(maxsize=None)
def cached_dummy_func(*args, ttl_dummy, **kwargs):
del ttl_dummy
return func(*args, **kwargs)
# 自動でハッシュ値を計算してダミー引数に入力する関数を作成
@wraps(func)
def ttl_cached_func(*args, use_cache=use_cache, **kwargs):
if not use_cache:
ttl_cached_func.cache_offset += 1
hash = get_ttl_hash(ttl_seconds) + ttl_cached_func.cache_offset
return cached_dummy_func(*args, ttl_dummy=hash, **kwargs)
ttl_cached_func.cache_offset = 0
return ttl_cached_func
return ttl_cache_deco
Example
以下、使用例です。
意図通りの動作が確認できました。
# In [1] --------------------
import random
from python_utils import ttl_cache
@ttl_cache(ttl_seconds=20)
def get_random_int():
return random.randint(0, 100)
# In [2] --------------------
get_random_int()
# Out: 64
# In [3] --------------------
get_random_int()
# Out: 64
# In [4] --------------------
get_random_int(use_cache=False)
# Out: 36
# In [5] --------------------
get_random_int(use_cache=False)
# Out: 0
# In [6] --------------------
get_random_int(use_cache=False)
# Out: 91
# In [7] --------------------
get_random_int()
# Out: 91
# In [8] --------------------
get_random_int()
# Out: 91
おまけ: use_cache
オプションをコード補完で見えるようにする
現状ではVSCodeのようなコード補完機能のあるエディタにおいて、
ttl_cache
デコレータを使った関数でuse_cache
オプションのコード補完は動作しません。
少し強引ですが、以下のように関数のシグネチャに追加すると、コード補完で表示されるようになります。
import inspect
...
def ttl_cache(ttl_seconds=3600, use_cache=True):
...
def ttl_cache_deco(func):
...
ttl_cached_func.cache_offset = 0
# 元の関数のシグネチャをコピーし、"use_cache"パラメータを追加
sig = inspect.signature(func)
params = list(sig.parameters.values())
params.append(inspect.Parameter("use_cache", inspect.Parameter.KEYWORD_ONLY, default=use_cache))
new_sig = sig.replace(parameters=params)
ttl_cached_func.__signature__ = new_sig
return ttl_cached_func
return ttl_cache_deco
まとめ
PythonでTTLキャッシュ機能を実装するシリーズは、ここで一旦終了です。
他にも以下のような改修案が考えられます。必要に応じてカスタマイズしてみてください。
改修案
ttl_seconds
やuse_cache
を指定しなくても使えるようにする。maxsize
を変更できるようにする。use_cache
を関数の引数としてではなく属性として追加する。
Note
-
今回はサードパーティ製ライブラリを使用せずに実装を行いましたが、
cachetools というライブラリにもTTLキャッシュ機能を持つデコレータが存在します。 -
lru_cache
の仕様上、キャッシュ機能を追加する関数の引数はハッシュ化可能である必要があります。
リストを入力とする関数などは今回のキャッシュデコレータを直接使用することは出来ないため、
入力をタプルにする等の調整が必要になります。