【Python】”@(アットマーク)”から始まる行はどういう意味?【デコレータ】

今回はPythonのソースコードでたまに見かける”@(アットマーク)”から始まる文(デコレータ)について、それがどういう意味を持つのかを解説していきます。
Pythonのソースコードを見ている時、たまにこういう記述はないでしょうか?
1 2 3 4 5 6 |
@outputMessage def test(): print("Hello World") if __name__ == "__main__": test() |
test()関数の前の行に、「@outputMessage」というよくわからない文がありますね。
これは一体なんなのでしょうか?
@(アットマーク)から始まる行は、デコレータ
このような文のことをデコレータと言います。
デコレートには飾り付ける、修飾するという意味がありますが、Pythonとしてのデコレータもまさにその通り、デコレータの以下の関数をデコレートする機能を持ちます。
このデコレータ、様々なパターンがあって理解するのが難しいです。
今回は一番簡単なパターンを紹介します。
デコレータが1つ、引数なしのテンプレパターン
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def outputMessage(func):#デコレータ def wrapper(): print("start!!") result = func()#これはtest() print("end!!") return result return wrapper @outputMessage def test(): print("Hello World") return "OK" if __name__ == "__main__": print(test()) |
これが最も簡単なパターンです。この関数を実行すると以下のような結果になります。
1 2 3 4 |
start!! Hello World end!! OK |
デコレータがなければtest関数内で”Hello World”、
メイン関数でtest関数の復帰値である”OK”
のみが出力されるはずですが・・・”Hello World”の間に”start!!”,”end!!”が挟まっていますね。
デコレータを使ってできること
1 2 3 4 5 6 7 8 9 10 |
def outputMessage(func):#デコレータ def wrapper(): print("start!!") result = func()#これはtest() print("end!!") return result return wrapper @outputMessage def test(): |
実は@outputMessageがついた状態でtest関数を実行すると、test関数ではなくtest関数を引数としてoutputMessage関数を実行します。
test()関数を引数にするということは・・・?
test()関数を変数として、前後の処理を好きに配置してデコレートできるということじゃありませんか!
つまりoutputMessage関数内では、test関数実行の前後に”start”,”end”を出力するようデコレートしているのです。
これで何ができるのかというと
例えば
- 関数の処理時間を計算したい場合は、それを出力するデコレータを作成!
- 関数単位で進捗状況を出力したい場合は、引数の関数名を取得して出力するデコレータを作成!
あとはデコレートしたい関数の上に”@”付きで配置して実行するだけでOK!
なんてことができます。
特に、複数の関数に同じことをしたい場合に大変便利ですね。
さて、もう少し詳しく中身をみていきましょう。
デコレータ関数の中身
1 2 3 4 5 6 7 |
def outputMessage(func):#デコレータ def wrapper(): print("start!!") result = func()#これはtest() print("end!!") return result return wrapper |
outputMessage関数の引数funcはtest関数です。
このoutMessage関数は、wrapperを復帰値として返していますね。
wrapper関数では、”start”,”end”の出力の間にfunc(test)関数を実行、それをresultに入れ、復帰値として返します。
つまり最終的に、outputMessage関数はresult,つまりtest関数の復帰値を返すということです。
test関数の復帰値は”OK”という文字列です。処理の順番をフローにすると
“start”を出力→test関数を実行(“Hello World”を出力)→”end”を出力→test関数の復帰値を受け取ったメイン関数で”OK”を出力
となります。
では、以下の場合はどうなるでしょう?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def outputMessage(func):#デコレータ print("start!!") result = func()#これはtest() print("end!!") return result @outputMessage def test(): print("Hello World") return "OK" if __name__ == "__main__": print(test()) |
wrapper関数を無くしてみました。この場合はどのように出力されると思いますか?
ヒントはoutputMessage関数の復帰値です。
正解はこうです。
1 2 3 4 5 6 7 |
start!! Hello World end!! Traceback (most recent call last): File "decorator1.py", line 13, in <module> print(test()) TypeError: 'str' object is not callable |
“OK”が出力されずに、エラーが表示されてしまいました・・・。
outputMessage関数の復帰値を考えてみましょう。
wrapperがあった場合の復帰値は、wrapper関数
wrapperがない場合の復帰値は、result・・・つまり”OK”という文字列
返すものが全く違いますね。メイン関数をみると「print(test())」となっていますが、文字列に「()」はつけませんよね。
逆に「print(test)」なら”OK”が出力されるということです。
test関数はあくまで関数なので、デコレータの復帰値も関数となるように内部関数を定義しましょう。
まとめ
今回はPythonのソースで見かける”@(アットマーク)”から始まる文、デコレータについて紹介しました。
関数の前後に決まった処理をデコレートしたい時があれば、ぜひ使ってみてください。
今回紹介したのはデコレータの一番簡単な例で、他にも
- デコレータが複数存在する
- デコレータの引数に関数ではなく*args,**kwargsを指定する
等のパターンもあります。これらについては別の機会に紹介したいと思います。