Pythonのデコレータをわかりやす解説

デコレータpython
  • 「デコレータ」とは関数やクラスの前後に(それら自体の定義は変更せず)処理を追加できる機能のこと
  • 「@〜」を付けるだけで出来てしまう
  • デコレータで実現出来ること
    • 関数の引数チェック
    • 関数呼び出し結果のキャッシュ
    • 関数の実行時間の計測

 

(今回は”関数デコレータ”を扱います。まぁクラスデコレータも同じようなものよ。)

 

「@~」の部分のコードの表示がコメントアウトみたいになってますが、実際に書く部分なので注意してください。

スポンサーリンク

デコレータの具体例をたくさん見よう

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を書いている」...
タイトルとURLをコピーしました