(今回は”関数デコレータ”を扱います。まぁクラスデコレータも同じようなものよ。)
「@~」の部分のコードの表示がコメントアウトみたいになってますが、実際に書く部分なので注意してください。
functools.lru_cached()
functools.lru_cached()は、関数の結果をキャッシュしてくれるデコレータです。
同じ引数 での呼び出し結果が既にキャッシュされている場合は、関数を実行することなくキャッシュ済みの結果を返します。
(キャッシュの際、関数の引数と結果の対応付けに辞書が使われるため、@lru_cached()を付けた関数の引数は、辞書のキーに使えるイミュータブルなオブジェクトでないとアカン)
from functools import lru_cache from time import sleep ------------------------------------- @lru_cache(maxsize = 32) #最近の呼び出し最大32回文までキャッシュ def plus_one(n): sleep(3) return n + 1 ------------------------------------- plus_one(99) #しっかり3秒スリープしてから出力する ------------------------------------- #出力 100 ------------------------------------- plus_one(99) #速攻で結果出力される ------------------------------------- #出力 100
関数の呼び出し時にログを出力するデコレータ
関数デコレータの実体は、引数に関数を1つ受け取る呼び出し可能オブジェクトです。
つまりプログラムの実行中にデコレータが戻り値として返した、新しい関数が元の関数名に紐付けられている。
def deco(f): print('deco called') def wrapper(): print('before exec') v = f() #元の関数を実行 print('after exec') return v #元の関数の結果を返す return wrapper ------------------------------------ @deco def func(): print('exec') return 1 ------------------------------------ #出力 deco called ------------------------------------ func.__name__ #新しい関数wrapperが元の関数名に紐付けられている ------------------------------------ #出力 'wrapper' ------------------------------------ func() #func()の呼び出しはwrapper()の呼び出しになる ------------------------------------ #出力 before exec exec after exec 1
上のデコレータdecoは、”デコレート対象の関数が引数をとるもの”だとエラーになってしまう。
それを改善する。
”プログラムの実行中に実際に呼び出される関数はwrapper()だから”、wrapper()関数に、元の関数が受け取った引数を渡してやればいい!
def deco_new(f): print('deco_new called') def wrapper(*args, **kwargs): print('before exec') v = f(*args, **kwargs) #引数をきちんと渡して元の関数を実行 print('after exec') return v #しっかりと関数の戻り値を返す return wrapper ------------------------------------ @deco_new def x_and_y(x, y): print('exec') return x, y ------------------------------------ #出力 deco_new called ------------------------------------ x_and_y.__name__ ------------------------------------ #出力 'wrapper' ------------------------------------ x_and_y(1, 2) ------------------------------------ #出力 before exec exec after exec (1, 2)
関数の処理時間を計測するデコレータ
このデコレータの名前をelapsed_time()とします。
「@elapsed_time」を関数の直前に付けるだけで、その関数の処理時間を計測できます。すごい!
import time from functools import wraps #便宜上使うデコレータ(説明は省略) def elapsed_time(f): @wraps(f) def wrapper(*args, **kwargs): start = time.time() v = f(*args, **kwargs) print(f"{f.__name__}: {time.time() - start}") return v return wrapper ------------------------------------ @elapsed_time def func(n): return sum(i for i in range(n)) ------------------------------------ func(100) ------------------------------------ #出力 func: 1.0013580322265625e-05 4950 ------------------------------------ func(101) ------------------------------------ #出力 func: 1.0013580322265625e-05 5050
参考書籍:
『Python実践入門ー言語の力を引き出し、開発効率を高める』
陶山嶺 著
WEB+DB PRESS plusシリーズ
技術評論社

Python実践入門 ──言語の力を引き出し、開発効率を高める
Pythonはここ数年で日本語の書籍も増え,開発現場での利用実績も着実に増えてきています。ご自身の第二,第三の言語の選択肢としてPythonが気になっているという方も多いのではないでしょうか。また,「Pythonを始めてみたけど,実際に業務で利用するには不安が残る」「コードレビューに怯えながらPythonを書いている」...