ヒトリ歩き

愚痴とかいろいろ書きます

lru_cacheデコレータを使ってみよう

ロバストPythonでも出てきたlru_cacheを試してみる。 キャッシュをデコレータだけで実現でき、標準で備わっているのでいい。

lru_cacheとは

関数をメモ化用の呼び出し可能オブジェクトでラップし、呼び出し最大maxsize回まで保存するデコレータ maxsizeを指定しない場合、上限なしで保存する。

docs.python.org

どんなときに使えるのか

処理に時間がかかる場合に使える。 キャッシュを利用した場合、キャッシュしたデータ元が更新され、キャッシュが更新されていない場合は古いデータが返却されてしまう。 そのため、常に最新データを返す必要がある場合は注意が必要。

使ってみる

呼び出し最大2回まで保存するように設定して実行してみる。

@lru_cache(maxsize=2)
def calculate(a):
    print("call calculate")
    r = 0
    for i in range(10000000):
        r += i

    return r

def main_call():

    for i in range(5):
        calculate(1)
        print(calculate.cache_info())

if __name__ == '__main__':
    main_call()

5回実行してprint文は1回だけなのでキャッシュが効いている。
しかし、説明によれば最大maxsizeまで保存すると書いてあるので、3回目でprint文がでるのかなと思ったが、違うみたい。

call calculate

CacheInfoを確認してみる

ラップされた関数には、cache_info()関数が追加されるようなので、cache_info()関数をコールして、CacheInfoを確認する。 currsizeは、常に1になっている。どうやら、キャッシュで保持しているデータの数みたい。

call calculate
49999995000000
CacheInfo(hits=0, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=1, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=2, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=3, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=4, misses=1, maxsize=2, currsize=1)

追加で以下のプログラムで動作を確認してみる。

@lru_cache(maxsize=2)
def calculate(a):
    print("-- Call calculate function")
    r = 0
    for i in range(10000000):
        r += i

    return r

def main_call():

    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("cacheが効いているはず -------")
    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("1のキャッシュが削除 -------")
    print("calculate 3")
    calculate(3)
    print(calculate.cache_info())

    print("2と3のcacheが効いているはず -------")
    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("calculate 3")
    calculate(3)
    print(calculate.cache_info())

    print("1のcacheがない -------")
    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

実行結果からcalculate 3でキャッシュミスが発生し、calculate 3のキャッシュが登録されて、再度calculate 1の実行で、キャッシュミス(misses)が発生していることがわかる。
デコレータの説明では、最大maxsize回まで保存するとなっているけど、最大maxsize個まで保存するの方が説明としては正しいのかなと。(文章の理解力の問題か・・・)

calculate 1
-- Call calculate function
CacheInfo(hits=0, misses=1, maxsize=2, currsize=1)
calculate 2
-- Call calculate function
CacheInfo(hits=0, misses=2, maxsize=2, currsize=2)
cacheが効いているはず -------
calculate 1
CacheInfo(hits=1, misses=2, maxsize=2, currsize=2)
calculate 2
CacheInfo(hits=2, misses=2, maxsize=2, currsize=2)
1のキャッシュが削除 -------
calculate 3
-- Call calculate function
CacheInfo(hits=2, misses=3, maxsize=2, currsize=2)
2と3のcacheが効いているはず -------
calculate 2
CacheInfo(hits=3, misses=3, maxsize=2, currsize=2)
calculate 3
CacheInfo(hits=4, misses=3, maxsize=2, currsize=2)
1のcacheがない -------
calculate 1
-- Call calculate function
CacheInfo(hits=4, misses=4, maxsize=2, currsize=2)

まとめ

functools.lru_cacheは簡単にキャッシュを実現でき、標準でPythonに組み込まれているので、OSSを改めてインストールする必要がないので、プロジェクトの関係でOSSが利用できないプロジェクトでも導入がしやすいと考える。
自分自身も使う機会があれば、積極的に使ってみたい。

参考

www.fujitsu.com