Pythonのデコレータの最もシンプルな例(Progate修了レベル)
ProgateでPythonのレッスンを修了したばかりのあなたへ。「Pythonマスターしたぜ!」の気分になってDjangoやらFlaskやら実践編をぐぐってみたところわけのわからない「@(アットマーク)」記法にめまいがしている頃かと存じます。
心配無用、私のことです。この記事で10分で@をマスターしましょう。
@記法の正体
デコレータというらしいです。
Pythonはオブジェクト指向の言語(RubyやSwiftなどもそう)なので、関数もオブジェクトです。
関数がオブジェクトであるとは
つまりこんなこともできるということです。
関数自体を変数に代入
def func(int): return int + 1 var = func print(var(1))
実行結果
2
関数の引数に関数
関数の引数に別の関数を使うこともできます。
def func1(func,text): return func + text def func2(text): return text print( func1(func2('Hello'),' world') )
実行結果
Hello world
引数にとった関数の引数を使いたい
デコレータとは、「関数を引数にとって、新しい関数を返す」関数と考えてもらってOKです。
▷厳密にいうと関数ではなく「callable」というらしいですが、まあ関数と思っていていいでしょう。ちなみに関数はcallableの一種だそう。
さて、関数A(関数B( C ))
という例を考えましょう。
関数Bを引数にとるからには、その引数 C を関数Aの中で使いたいですね。
しかしこんな書き方はシンタックスエラーになります。
def func1(func(arg),text): #実行結果:SyntaxError: invalid syntax
困りましたね。これではfunc1(関数A)にarg(関数Bの引数 C )を渡せません。
これを解決するには、こんな書き方をしなければなりません。
def func1(func,text1): def innerfunc(arg): def func3(text3): return ' [' + text3 + '] ' return func(arg) + text1 + func3(arg) + arg return innerfunc def func2(text2): return text2 print( func1(func2,' world')('Hello') )
実行結果
Hello world [Hello] Hello
ちょっとごちゃついてしまいましたが、みてほしいのはfunc2
の引数である'Hello'
をfunc1
の中で自由に使っている点です。
上のサンプルコードの解説
上でなにをやっているのかというと、
まずfunc1
に引数をいれる
func1(func2, ' world')
この段階ではfunc2
に引数は入れません。(引数をいれた関数=実行結果です。ここでは関数そのものを渡すので、引数は入れない。)
▷sum([1,2])
は数値3
であって関数sum
ではないですね。
すると、これはすなわち関数innerfunc
を返します。
そうしたら、返された関数innerfunc
に引数をわたせばよいのです。
func1(func2,' world')('Hello') #func1(func2,' world') == innerfunc なので #func1(func2,' world')('Hello') == innerfunc('Hello')
そうしたら、あとはfunc1
の中のinnerfunc
に引数'Hello'
を入れた結果が出力されます。
さて、デコレータを使おう
デコレータは、上のコードがあんまりごちゃごちゃしているので簡単にまとめてしまおうというものです。
さっきのコードをデコレータを使って書くとこうなります。
※「wrapper
」というのが増えてしまっていますが、これはあとで説明します。
def func1(text1): def wrapper(func): def innerfunc(arg): def func3(text3): return ' [' + text3 + '] ' return func(arg) + text1 + func3(arg) + arg return innerfunc return wrapper @func1(' world') def func2(text2): return text2 print( func2('Hello') )
実行結果
Hello world [Hello] Hello
つまりfunc2 == デコレータ無しの場合の func1(func2,' world')
ということです。
一般化して書くとこうなります。
@func1(引数A) def func2(引数): #と書いたとき、 func2(引数B) #はデコレータがなかった時の func1(func2(引数B),引数A) #を表す
func1の引数(wrapperを使う)
func1
(関数A)にfunc2
(関数B)以外の引数を入れる場合(上のケースがそうでしたね)は、func1(func,arg):
のような形のままデコレータを使うとシンタックスエラーになってしまいます。
そういうときはwrapperを使います(別に名前はなんでもいいです)。
書き方は上のケースを参考にしてください。func1
自体の引数には関数は入れません。関数を引数にとる関数を内部で新たに定義し、ワンクッション置く形になります。
wrapperが要らない場合
func1
(関数A)の引数にfunc2
(関数B)しか入れない場合はこの書き方は不要です。その場合はこんな風に書けばOKです。
def func1(func): def innerfunc(arg): def func3(text3): return ' [' + text3 + '] ' return func(arg) + ' world' + func3(arg) + arg return innerfunc @func1 def func2(text2): return text2 print( func2('Hello') )
実行結果
Hello world [Hello] Hello
デコレータを使わずに書くとこうなります。
def func1(func): def innerfunc(arg): def func3(text3): return ' [' + text3 + '] ' return func(arg) + ' world' + func3(arg) + arg return innerfunc def func2(text2): return text2 print( func1(func2)('Hello') )
まとめ
デコレータがわからないと、フレームワークはすべて呪文になってしまいます。そもそもそういうブラックボックスが嫌だからRuby on Railsを避けてPythonを選んでるんでしょ?(僕のことです)
ちなみにデコレータは複数つけられます。(その場合、実行は下からなので注意)
@func2 @func1 def func3(text): return text
ご質問などあれば気軽にコメントにどうぞ!
余談:Flaskでよくみる@app.route('/')
Flaskのルーティングで@app.route('/')
みたいのがよくでてきますね。意味を知らなくても使えるしすごい楽なんだけど、これもデコレータですね。
そもそもapp
については上の方でこういうコードで作ってると思います。
from flask import Flask app = Flask(__name__)
または(この違いがわからない人はProgateに戻りましょう)
import flask
app = flask.Flask(__name__)
__name__
はProgateでも習った__init__
と形が似てますね。こういう人間が作りそうにない形のものはだいたいシステムで決まったものですよね。
__name__
にはモジュールの名前が自動的に代入されています。Flask(__name__)
つまり引数に__name__
をとるFlask
クラスのインスタンスがapp
です。
Flask
クラスにはメソッドroute(self, rule, **options)
が定義されているので、このrule
に引数としてパス(URL)を渡すわけですね。
このへんの中身を全部把握しようとすると骨が折れるので不要と思います。ここはこんな理解でOKです↓。
@app.route('パス') def パスごとの処理
__name__
について詳しくはこちらがわかりやすかったです。
azuuun-memorandum.hatenablog.com