Pythonのデコレータは算数っぽく考えるとわかりやすかった
デコレータはなかなか理解するのが難しかったんだが、算数っぽく考えると割とすんなり飲み込めたのでメモする。
前提としてPythonは、関数内で関数を呼び出せる、関数を引数や関数の戻り値とできることを確認しつつ、ちょっとずつ考えていく。
関数内で関数を呼び出す
main関数の中にsub関数を作って、main()の実行結果を出力する。
>>> def main(): ... def sub(): ... print('This is sub.') ... return sub ... >>> main() <function main.<locals>.sub at 0x10ee6a280>
結果は関数オブジェクトが返ってくる。これは納得、main()でsubをリターンしているんだから。
ではsub関数の内部のprint()を実行するにはどうするか。
main()の戻り値=subの関数オブジェクト
になるんだから、算数っぽく考えると
main() = sub
の両辺に()をプラスして、
main()() = sub()
となるはず。
>>> main()()
This is sub.
おk。
関数に引数として関数を渡す
関数の引数に関数を指定できることを確認する。
>>> def main(func): ... return func ... >>> def sub(text): ... return text ... >>> main(sub) <function sub at 0x10ee6a1f0>
main関数の引数をしてsub関数を指定すると、return func
で引数にしたオブジェクトがそのまま返ってくる。これも納得。
ではsub関数の処理を実行するには、算数っぽく考えると
main(sub) = sub
の両辺に()をプラスして、
main(sub)('あつい') = sub('あつい')
となるはず。
>>> main(sub)('あつい') 'あつい'
デコレータにしてみる
ここまで理解できればデコレータも余裕。
call関数をデコレートして、処理の前後に別の処理を入れてみる。
# デコレートする側 >>> def main(func): ... def sub(text): ... print('sub 1.') ... func(text) ... print('sub 2.') ... return sub ... # デコレートされる側 >>> def call(text): ... print(text) ... >>> main(call) <function main.<locals>.sub at 0x102cef4c0>
main関数の引数にcall関数オブジェクトを指定する。
main(call) = sub
になるから、両辺に()をプラスして、
main(call)('あつい') = sub('あつい')
となるはず。
>>> main(call)('あつい') sub 1. あつい sub 2.
これによりcall関数の処理の前後に別の文字列を表示することができている。 これがデコレータ。
最後に、Pythonicに@マークを使って書き換えてみる。
>>> def main(func): ... def sub(text): ... print('sub 1.') ... func(text) ... print('sub 2.') ... return sub ... >>> @main ... def call(text): ... print(text) ... >>> call('なんでこんなにあついねん') sub 1. なんでこんなにあついねん sub 2.
今までのことを踏まえると、call('text')はmain(call)('text')と同じということになる。
callの前後にmainの処理を差し込むイメージ。この考え方が適切なのかどうかは不明だが、なんとなく理解できた。
算数っぽくとかって書いたけど、そもそも算数って両辺に同じ数足しても問題ないんだっけ?とかなってる。何も覚えていない。