Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
functools.lru_cache() / functools.wraps()
『functools.lru_cache()』は関数の結果をキャッシュするデコレータで、同じ引数で呼び出された場合に再計算せず前回の結果を返します(メモ化)。『functools.wraps()』はデコレータを作る際に、元の関数のメタデータ(名前・docstringなど)を保持するためのデコレータです。lru_cacheを使うと再帰計算など繰り返し同じ計算をする処理を大幅に高速化できます。
構文
from functools import lru_cache, cache, wraps
# LRUキャッシュ(最大サイズ指定)
@lru_cache(maxsize=128)
def func(args):
...
# 無制限キャッシュ(Python 3.9+)
@cache
def func(args):
...
# デコレータ作成時のメタデータ保持
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
関数一覧
| 関数・属性 | 概要 |
|---|---|
| lru_cache(maxsize=128) | LRU方式でキャッシュするデコレータ。maxsize=Noneで無制限。 |
| cache() | maxsize=Noneのlru_cacheと同等。Python 3.9以降で使用可能。 |
| func.cache_info() | ヒット数・ミス数・最大サイズ・現在のサイズを返す。 |
| func.cache_clear() | キャッシュを全クリアする。 |
| wraps(func) | デコレータ内のwrapperに元のfuncのメタデータをコピーする。 |
サンプルコード
from functools import lru_cache, cache, wraps
import time
# lru_cache: フィボナッチ数列の高速化
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(50)) # 12586269025(一瞬で計算)
print(fib.cache_info()) # CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)
# キャッシュのクリア
fib.cache_clear()
# lru_cache: maxsizeを指定したLRU
@lru_cache(maxsize=4)
def slow_func(n):
time.sleep(0.1) # 重い処理をシミュレート
return n * n
# cache(Python 3.9+): 無制限キャッシュの簡易記法
@cache
def expensive(x, y):
return x ** y
# wraps: デコレータ作成時にメタデータを保持する
def timer(func):
@wraps(func) # これがないと__name__が'wrapper'になってしまう
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} の実行時間: {time.time() - start:.4f}秒")
return result
return wrapper
@timer
def greet(name):
"""あいさつを返す関数"""
return f"こんにちは、{name}さん!"
print(greet('田中')) # こんにちは、田中さん! + 実行時間
print(greet.__name__) # 'greet'(wrapsがなければ'wrapper'になる)
print(greet.__doc__) # 'あいさつを返す関数'
概要
『lru_cache()』のLRUはLeast Recently Used(最近最も使われていない)の略で、maxsizeを超えてキャッシュが溢れた場合に最も長く使われていないエントリを削除します。キャッシュされる関数の引数はハッシュ可能(hashable)である必要があるため、リストや辞書は引数に使えません。タプルや文字列、数値など不変の型を使うか、引数をタプルに変換する工夫が必要です。
『wraps()』はデコレータを適切に実装するためのベストプラクティスです。wrapsを使わないと、デコレートされた関数の『__name__』や『__doc__』が内部のwrapper関数のものに書き換わってしまい、デバッグやドキュメント生成で問題が起きます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。