質問の背景:
メモ化は、同じ引数で同じ関数が繰り返し呼び出される場合に、最適化手法として広く使われている技法で、関数型プログラミング、再帰的計算、および動的プログラミングで使用されます。Pythonでは、functools.lru_cacheデコレーターの登場と計算結果のキャッシングパターンによって取り入れられました。
問題:
キャッシングなしでは、数値フィボナッチを計算するような再帰関数は非常に遅く動作します:各引数の計算が何度も繰り返されるため、呼び出し回数が指数的に増加します。
解決策:
メモ化は、すでに計算された値を(例えば、辞書に)保存しておき、必要に応じて再利用します:
手動実装の例:
memo = {} def fib(n): if n in memo: return memo[n] if n <= 1: result = n else: result = fib(n-1) + fib(n-2) memo[n] = result return result
Python 3.2以降では、デコレーターを使用するだけで済みます:
from functools import lru_cache @lru_cache(maxsize=128) def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2)
主な特徴:
可変引数(例えば、リスト)を持つ関数をメモ化できますか?
いいえ、lru_cacheはすべての引数がハッシュ可能(不変)である必要があるため、TypeErrorが発生します。
from functools import lru_cache @lru_cache # def f(a): return a[0] # TypeError: unhashable type: 'list'
メモ化された値は自動的に削除されますか?
はい、maxsizeが設定されている場合、制限を超えた値は削除されます(最も最近使用されていない)。無限キャッシュ(maxsize=None)は、メモリが溢れる原因になることがあります。
メモ化された関数が新しい引数で自分自身を呼び出すとどうなりますか?
メモ化はそれでも機能します — 各ユニークな引数セットの結果は1回だけ計算され、キャッシュされます。
サイズ制限のない独自のキャッシュを作成する:
cache = {} def foo(x): if x not in cache: cache[x] = long_heavy_computation(x) return cache[x]
しばらくして、プログラムはギガバイトのメモリを使用します。
利点:
欠点:
maxsizeを使用したlru_cacheの使用:
from functools import lru_cache @lru_cache(maxsize=1000) def foo(x): return long_heavy_computation(x)
利点:
欠点: