デコレータによる処理の追加と変更

デコレータによる処理の追加と変更

デコレータ

 高階関数を上手に使うと、ある関数を引数に受け取って、その関数の中身を変えることなく、高階関数自身に定義されている機能を付け加えることができます。そのようなコードのシンタックスシュガー(簡略記法)が デコレータ (decorator) とよばれる構文です (decorator には「装飾するもの」という意味があります)。デコレータを使えば処理内容が微妙に異なるだけの関数をあれこれ作る必要がなくなります。

デコレータによる機能の追加

 デコレータの記法を使うときは、まずデコレータを定義してから、デコレート(装飾)される関数の前に、

 @デコレータ名

を添えます。簡単な例を見てみましょう。

# 高階関数の定義
def hello(func):
    def inner():
        print("Hello!")
        func()
    return inner

# how()関数をhello()関数で装飾
@hello
def how():
    print("How's it going?")

how()
Hello!
How's it going?

 how() は "How's it going?" (調子はどう?) という文字列を表示する関数です。hello() は関数を受け取って、

 ・"Hello!" と表示する
 ・受け取った関数を実行する

という inner()関数を作って返す関数です。how() を hello() でデコレート(装飾)し、how() を実行すると、

 ・"Hello!" と表示する
 ・ how() を実行して "How's it going?" を表示する

という結果になります。つまり元々の how()関数が持つ機能に、"Hello!" と表示する機能が追加されたことになります。

 冒頭に説明したように、デコレータ機能はあくまで単なるシンタックスシュガー(簡略記法)に過ぎないので、本質は高階関数の仕組みにあるということに注意してください。上のサンプルコードは次のコードをデコレータを使って書き換えただけです。

# 高階関数の定義
def hello(func):
    def inner():
        print("Hello!")
        func()
    return inner

# how()関数の定義
def how():
    print("How's it going?")

# hello()にhow()を渡して実行
hello(how)()
Hello!
How's it going?

 コードを比較すると、デコレータを使った記法がさほど簡略されていないように思えるかもしれませんが、hello(how)() のような書き方は普段見慣れないものなので、コードを読んだ人を戸惑わせてしまうかもしれません。この部分をもう少しわかりやすく記述すると次のようになります。

# 関数オブジェクトを生成
my_func = hello(how)

# 関数を実行
my_func()

 これならば hello() の引数に how() を渡して実行しているのだということは理解できますが、やや冗長な書き方です。デコレータの記法を用いると、hello() で how() をデコレートしていることがひと目でわかります。
 

デコレータによる機能の上書き

 さきほどのサンプルコードを再掲します。

# 高階関数の定義
def hello(func):
    def inner():
        print("Hello!")
        func()
    return inner

# how()関数をhello()関数で装飾
@hello
def how():
    print("How's it going?")

how()
Hello!
How's it going?

 inner() の関数ブロック(処理内容が書かれた部分)から func() という記述を取り去ってしまうと、どのような結果になるのかを予測するのは難しくないはずです。念のために確認しておきましょう。

# 高階関数の定義
def hello(func):
    def inner():
        print("Hello!")
    return inner

# how()関数をhello()関数で装飾
@hello
def how():
    print("How's it going?")

how()
Hello!

 予想通り "Hello!" とだけ表示されました。hello() は内側で定義した inner() を返すだけですから当然のことです。how() を受け取ってはいますが、内部で受け取った関数をひとつも使っていないので、結果としてデコレータは how() の機能を完全に上書きする形になります。
 

デコレータによる合成関数の実装

 合成関数 g(f(x)) を次のように定義します。
 

f(x) = x + 1, g(f(x)) = 2f(x)

 たとえば、x = 10 のとき、
 

f(10) = 10 + 1 = 11, g(10) = 2f(10) = 22

と計算できます。この合成関数を Python のデコレータを使って実装してみましょう。

# 高階関数の定義
def double_func(func):
    def inner(t):
        return func(t) * 2
    return inner

# add_one()をdouble_func()で装飾
@double_func
def add_one(x):
    return x + 1

x = add_one(10)

print(x)
22

 double_func() は「受け取った関数を 2 倍にする関数」を内部で定義します。引数に add_one() を渡せば、2 * (x + 1) を計算する関数となります。サンプルコードにあるように、デコレートされた関数に x = 10 を渡せば 22 が返ります。