はじめに
Pythonのデコレータについて解説します。
デコレータとは
デコレータとは関数を修飾するための仕組みです。
既存の関数に対して、関数の中身を変えることなく処理を追加することが出来ます。
[decorate]デコレート(飾る、装飾)が語源みたいだね。
デコレートよりチョコレートの方が好きだにゃー
デコレータの使い方
記述方法
関数定義の上側に@から始まるデコレート関数名を記述することで使用することが出来ます。
@デコレート関数
def 関数():
#関数の処理
・・・・・・・・・・
そういえばクラスを作成する時に定義する、クラスメソッド(@classmethod)やスタティックメソッド(@staticmethod)もデコレータを使ってるね。
サンプル
次のサンプルは「こんにちは。」と返すだけの関数です。
def func_aisatsu():
return 'こんにちは。'
print(func_aisatsu())
# 実行結果
# こんにちは。
この関数をデコレートして「こんにちは。今日も良い天気ですね。」と返すようにします。
def deco(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + '今日も良い天気ですね。'
return wrapper
@deco
def func_aisatsu():
return 'こんにちは。'
# 実行結果
# こんにちは。今日も良い天気ですね。
1行目にdecoという名前で関数を作成します。
引数にfuncをもたせます。
2行目にwrapperという名前で関数内関数を作成します。
引数に*args, **kwargsをもたせます。ここの説明は後述します。
3行目func(*args, **kwargs)の部分が、func_aisatsu()関数の処理タイミングになります。
「こんにちは。」という処理結果に「今日も良い天気ですね。」の文字を追加してreturnしています。
6行目に@decoを記述することでfunc_aisatsu()関数をデコレートしています。
デコレータを使用すると、関数の中身を変更することなく処理を追加することができるにゃー。
関数の処理時間を表示するサンプル
もう一つサンプルを見てみましょう。
import time,timeit, datetime
def deco_time(func):
def wrapper(*args, **kwargs):
print(datetime.datetime.now().strftime('開始:%H:%M:%S')) #処理を開始する前の時刻
#デコレータ先の関数を実行して処理時間を取得する。
result = timeit.timeit(lambda: func(*args, **kwargs), number=1)
print('経過時間:{}'.format(result))
print(datetime.datetime.now().strftime('終了:%H:%M:%S')) #処理が終了した後の時刻
return wrapper
@deco_time
def test_func():
for x in range(3):
#時間のかかる処理
time.sleep(1)
test_func()
# 実行結果
# 開始:09:30:23
# 経過時間:3.009300909
# 終了:09:30:26
11行目にtest_funcという名前で時間のかかる処理を行います。
どのくらい処理に時間がかかっているのか調べたいので、デコレータで処理時間を確認できるようにしました。
他の関数の処理時間を調べたいときも、その関数に@から始まるデコレータ関数を1行追加するだけなので汎用的に使うことができるね。
引数を持つ関数をデコレートする
デコレートされる側の関数に引数を持っている場合の処理を見てみます。
具体的には下記赤文字の引数を渡す場合です。
@デコレート関数
def 関数(引数):
#関数の処理
・・・・・・・・・・
サンプルを見てみましょう。
def deco_start_msg(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + 'はじめました。'
return wrapper
@deco_start_msg
def func_msg(msg):
return msg
print(func_msg('冷やし中華'))
# 実行結果
# 冷やし中華はじめました。
先に作成した引数なしの場合と比べてデコレート関数は特別変化ありません。
引数があればwrapper関数の*argsもしくは**kwargsに値が入ります。
なければ*argsと**kwargsは空の状態になります。
*argsと**kwargs
引数が2つ以上の場合はどうなるにゃ?
引数の数を2つにしてみましょう。
また、確認のためにargsの型と値をprint関数で出力します。
def deco_start_msg(func):
def wrapper(*args, **kwargs):
print('argsのTypeは{}'.format(type(args)))
print('argsの値は{}'.format(args))
return func(*args, **kwargs) + 'はじめました。'
return wrapper
@deco_start_msg
def func_msg2(msg, msg2):
return msg + msg2
print(func_msg2('冷やし中華', 'ゴマダレ味'))
# 実行結果
# argsのTypeは<class 'tuple'>
# argsの値は('冷やし中華', 'ゴマダレ味')
# 冷やし中華ゴマダレ味はじめました。
この実行結果からargsはタプル型であり、2つの引数がargsに入っていることがわかります。
デコレートされる関数の引数がいくつなのか、デコレートする関数は気にする必要がないってことだね。
argsの頭についている*(アスタリスク)はなんだろにゃ?
引数名の頭に*を付けると任意の数の引数を指定することが出来ます。
そしてその引数はタプルとして扱われるのです。
これを可変長引数といいます。
それでは**kwargsのように*が2つ付いているのはなんでしょうか?
実はこれも可変長引数になります。
*が2つある場合は辞書として扱われます。
- *args *が一つは引数がタプルとして扱われます。
- **kwargs *が二つは引数が辞書として扱われます。
なお、可変長引数はデコレータ関数以外でも使用できます。
デコレータに引数を渡す
次は、デコレート関数に引数を渡す方法を見ていきます。
@デコレート関数(引数)
def 関数():
#関数の処理
・・・・・・・・・・
サンプルを見ていきましょう。
def deco_start_msg_price(price):
def deco_start_msg(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + 'はじめました。' + price
return wrapper
return deco_start_msg
@deco_start_msg_price('¥890')
def func_msg2(msg, msg2):
return msg + msg2
print(func_msg2('冷やし中華', 'ゴマダレ味'))
# 実行結果
# 冷やし中華ゴマダレ味はじめました。¥890
これまでのデコレータ関数を更にネストして、1行目に新しく値段を引数で渡せる関数を追加しました。
4行目で、値段の引数を使用しています。
サンプルを見ただけでは分かりづらいにゃあ。
これは実際に入力をしてみれば処理の流れが分かりやすくなるよ。
複数のデコレータを付ける
デコレータを複数付けてみます。
@デコレート関数1()
@デコレート関数2()
def 関数():
#関数の処理
・・・・・・・・・・
下のようなサンプルを作成してみました。
def add_deco(func):
def wrapper(*args, **kwargs):
print('■' * 40)
func(*args, **kwargs)
print('■' * 40)
return wrapper
def deco_start_msg_price(price):
def deco_start_msg(func):
def wrapper(*args, **kwargs):
print(func(*args, **kwargs) + 'はじめました。' + price)
return wrapper
return deco_start_msg
@add_deco
@deco_start_msg_price('¥890')
def func_msg2(msg, msg2):
return msg + msg2
func_msg2('冷やし中華', 'ゴマダレ味')
# 実行結果
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# 冷やし中華ゴマダレ味はじめました。¥890
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
1行目に新しくadd_decoというデコレータ関数を追加しました。
これは出力メッセージの上下に■を40個配置する関数です。
15、16行目にデコレータを付けています。
複数のデコレータがある場合、上から順に実行されます。
つまりサンプルではadd_deco関数から始まり、3行目の■が40個がprint出力されます。
次に4行目でfuncが呼ばれるので、ここで2つ目のデコレータ(deco_start_msg_price)が開始され、
11行目のメッセージがprint出力されます。
2つ目のデコレータ処理はこれだけなので、また1つ目のデコレータにもどり
5行目の■40個がprint出力されます。
複数デコレータが付いている場合は上から順に実行されることを覚えよう。
まとめ
Pythonのデコレータについて解説しました。
- デコレータは関数の処理を変更することなく、処理を追加することができる。
- 引数をもつ関数をデコレートした場合は可変長引数として、タプルか辞書の形で参照できる。
- デコレータに引数を付けることも可能。
- デコレータは複数つけることができ、上から順に実行する。
デコレータを使用しているライブラリとして、WEBアプリケーションフレームワークであるFlaskが挙げられます。
Flaskについては【Python】Flask入門を見てください。
最後まで読んでいただき、ありがとうございます。