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()が使える- 引数はハッシュ可能。メソッドには付けない



