デコレータ

デコレータ

 本記事では Python の デコレータ について説明します。以前の記事をあらためて読み直すと、すごく分かり難かったので、思い切ってリライト(書き直し)してみました。スモールステップで、なるべく丁寧に分かりやすく書くことを心がけたので、以前に読まれて「なんだかよくわからなかったよ」という方も、もう一度読んでいただけたら嬉しいです。

デコレータで関数に機能を追加する

 簡単な例から始めます。最初に関数オブジェクト func を受け取って、内部で定義した関数を返す関数 deco を定義します(このような関数を 高階関数 といいます)。

# PYTHON_DECORATOR

# In[1]

# ["Hello"を表示する関数]を返す関数を定義
def deco(obj):
    def hello():
        print("Hello!")
    return hello

 deco() は任意のオブジェクト obj を受け取るように設計されていますが、obj に対して何か処理するわけではありません。内部で定義した関数 hello() を返すだけです。hello() は "Hello!" を表示する関数です。ちなみに、deco は decorator の略で、装飾する側の関数として用意したので、こう名付けています。

 deco() を実行して、変数 my_func に格納してみます。繰り返しますが、deco() には何を渡しても動作に何の影響も与えません。何でもいいのですが、とりあえず数値 0 を渡すことにします。

# In[2]

my_func = deco(0)

 my_func には deco() の戻り値 hello が入っています。試しに実行してみましょう。

# In[3]

my_func()
Hello!

 もちろん、実行結果は文字列 "Hello!" となっています。

 さて、次に文字列 "Morning!" を表示する関数を定義します。

# In[4]

# "Morning!"を表示する関数を定義
def morning():
    print("Morning!")

 まず、そのまま実行してみましょう。

# In[5]

morning()
Morning!

 今度は morning() を deco で装飾 (デコレート) して再定義してから実行してみます。関数をデコレータで装飾するときは @ 記号を使います。

# In[6]

# morning()をdecoで装飾して再定義
@deco
def morning():
    print("Morning!")

morning()
Hello!

 不思議なことに、morning() で定められた処理は完全に無視されて、deco() の内部で定義された hello() の実行結果が表示されました。関数オブジェクト morning の正体を調べてみましょう。

# In[7]

morning
<function __main__.deco.<locals>.hello()>

 morning は deco のローカル関数 hello への参照となっています。まさに、この「参照の差し替え」がデコレータの本質なのです。

 ここまでの処理を @ を使わずに書き直すと、次のようになります。

# In[8]

def deco(obj):
    def hello():
        print("Hello!")
    return hello

def morning():
     print("Morning!")

deco(morning)()
Hello!

 @deco がなくなっている以外に、何も変わってないように思えますが、最後の行 deco(morning)() に注目してください。これは deco() に morning を渡して、戻り値の hello 関数を得て、() によって実行するという処理を表しています。

 普段、こんな書き方をあまりしないし、意味が捉えにくいし、可読性の低いコードと言えるかもしれません。デコレータは、こうした記法を避けるためのシンタックス・シュガー (簡略記法) です。

 しかし、これだけの説明では、デコレータ (装飾) のイメージが掴めません。では、morning に deco の内側で定義されている hello 関数の機能を付加するためには、どうしたらいいでしょうか?

 答えは簡単。hello() に受け取った関数を実行する一文を加えておけばいいのです:

# In[9]

def deco(func):
    def hello():
        print("Hello!")
        func()  # ここで受け取った関数を実行
    return hello

# morningをdecoで装飾
@deco
def morning():
    print("Morning!")

 morning を実行してみましょう。

# In[10]

morning()
Hello!
Morning!

 "Hello!" と "Morning!" が両方表示されました。
 皆さんも色々なテストコードを書いて、デコレータの使い方に慣れてください。