日々ろぐ

人に優しく٩( 'ω' )و

デコレータについてざっくりまとめ

タイトルの通り。

前回、高階関数についてざっくりまとめたので、その流れからデコレータもざっくりとまとめます。

デコレータとは?

最初はなんだか難しそうな感じがするなーと思っていましたが、紐解いてみると結構あっさりしてるものです。 端的に言ってしまえば、デコレータとは「処理をデコレーションして(別の処理で装飾して)置き換える記法」です。 高階関数を簡単に書くためのシンタックスシュガー・・と僕は考えています。 ポイントは「置き換える」ってところですかね。ある関数にデコレータを適用すると、その関数を呼ぶ時に必ずデコレーションされたもので呼び出されるようになります。

高階関数の書き方を振り返る

例によってこんな関数で考えてみます。

def outer(func):
    def inner():
        print("before execute")
        func()
        print("after execute")
    return inner()

def hello():
    print ("Hello World")

hello関数は文字の出力、outer関数は引数の関数実行前後に文字列を差し込むやつですね。 これをセットで呼ぶ時は、こんな感じに書くよって話だったと思います。

outer(hello)

ただ、いちいちこの書き方をするのは面倒だと思います。 「もうhello関数はouter関数とセットで使うから一緒に呼べるようにしとこうよ・・・」とい風に。

実際にデコレートしてみる

こういう場合は、outer関数をデコレータとしてhello関数を置き換えてあげましょう。

def outer(func):
    def inner():
        print("before execute")
        func()
        print("after execute")
    return inner()

@outer
def hello():
    print ("Hello World")

hello関数の上に「@outer」をつけました。 これは「hello関数をouter関数でデコレートしましたよ」ってことです。 こうすることで、hello関数を実行するときは、常に

outer(hello)

を実行することど同義になります。

つまり、デコレータをつけることで

hello = outer(hello)

としている(置き換えている)んですね。 中身を見てしまえばそう身構える必要もないかなって感じです。

ただ、注意としては・・ このデコレータを適用したhello関数を実行すると、実態として実行されているのはouter関数の中にあるinner関数が実行されています。

print(hello.__name__)

>>> inner

「置き換えている」ということを忘れがちですが、もともと定義したhello関数とは別物だと考えてください

そこで、「いやいやhello関数を読んでるんだからそこはhelloになるはずでしょ」と思う気持ちもわかります。 そのモヤモヤを解決するためにはfunctors.wrapsを使ってください。

def outer(func):
    @functors.wraps
    def inner():
        print("before execute")
        func()
        print("after execute")
    return inner()

実体となるinner関数に対してfunctors.wrapsをデコーレトしてあげることで、もとの関数の情報を保持しておくことができます。

print(hello.__name__)

>>> hello

あと、デコレータとして面倒なところは引数の受け渡しの部分がありますが・・・ 長くなるので別記事に。