Python functools.cache()とは?結果を丸ごとキャッシュする方法

Pythonのfunctools.cache()は、関数の結果を上限なしで覚えておくデコレータです。

Python 3.9で追加されました。実体はlru_cache(maxsize=None)と同じで、より短く意図が伝わる書き方です。


functools.cache()の基本的な使い方

from functools import cache

@cache
def square(n):
    print("  計算:", n)
    return n * n

print(square(4))
print(square(4))
print(square.cache_info())

実行結果:

  計算: 4
16
16
CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)

2回目は計算されず、hits=1になりました。maxsize=None——つまり上限なしです。


lru_cache()との違い

項目 @cache @lru_cache
保持数の上限 なし 既定128(変更可)
古い結果の破棄 しない 上限を超えたら捨てる
追加バージョン Python 3.9 Python 3.2
速度 わずかに速い 順序管理のぶん重い

cacheが少し速いのは、「どれが古いか」を管理する必要がないためです。捨てないと決めているぶん、処理が単純になります。

逆にいえば@cacheは永久にメモリを使い続けます。ここが唯一にして最大の注意点です。


いつcache()を使い、いつlru_cache()を使うか

状況 おすすめ 理由
引数のパターンが数十〜数百に限られる @cache 全部覚えても問題ない
再帰の途中計算 @cache 捨てると意味がない
設定・マスタデータの読み込み @cache 数が決まっている
ユーザーIDなど際限なく増える引数 @lru_cache 上限がないとメモリが尽きる
長時間動かすサーバー @lru_cache 使われない結果を捨てたい

判断は「引数の種類が有限で、数えられるか」の一点です。数えられるなら@cache、際限がないなら@lru_cache(maxsize=...)にしてください。


再帰処理での効果

from functools import cache

@cache
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

print(fib(50))
print(fib.cache_info())

実行結果:

12586269025
CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)

キャッシュがなければfib(50)は現実的な時間で終わりません。51回の計算で済んでいます。

再帰では途中の値がすべて再利用されるため、捨てるとかえって遅くなります。@cacheがぴったりの場面です。


キャッシュを消す

fib.cache_clear()
print(fib.cache_info())

実行結果:

CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)

@cacheにもcache_info()とcache_clear()がそのまま使えます。中身がlru_cacheだからです。

メモリが気になるときは、処理の区切りでcache_clear()を呼ぶという運用もできます。


Python 3.8以前で使うには

cacheは3.9で追加されたため、それ以前ではImportErrorになります。

from functools import lru_cache

@lru_cache(maxsize=None)
def square(n):
    return n * n

これがそのまま代替です。動作は完全に同じなので、古い環境を相手にするライブラリではこちらを使ってください。


引数の制約はlru_cache()と同じ

キャッシュのキーに使うため、引数はハッシュ可能でなければなりません。

from functools import cache

@cache
def f(x):
    return x

f([1, 2])

実行結果:

TypeError: unhashable type: 'list'

リスト・辞書・集合は渡せません。タプルやfrozensetに変換してください。

また、毎回結果が変わる関数には使えません。乱数・現在時刻・更新されるデータの取得などが該当します。詳しくは「Python functools.lru_cache()とは?計算結果をキャッシュする方法」で解説しています。


cached_propertyとの違い

クラスの属性をキャッシュしたいときはfunctools.cached_propertyがあります。

用途 使うもの
引数つきの関数 @cache
インスタンスごとのプロパティ @cached_property

@cacheをメソッドに付けるとselfもキャッシュのキーに含まれ、インスタンスが解放されなくなります。メソッドには使わないでください。


functools.cache()のまとめ

  • @cacheは上限なしのキャッシュ(Python 3.9以降)
  • 実体はlru_cache(maxsize=None)。わずかに速い
  • 引数の種類が有限なら@cache、際限なく増えるなら@lru_cache
  • 再帰処理との相性が非常に良い
  • cache_info()・cache_clear()が使える
  • 引数はハッシュ可能。メソッドには付けない

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら