デコレータについて整理

Pythonのデコレータについて、あやふやな理解しかしていなかったため、一度整理をおこないました。

What's decorator?

デコレータとは、関数に変更を加えるための関数です。すなわち、 関数を引数とする関数 と言うことが出来ます。

例えば、以下のようなプログラム addition.py を作成します。このプログラムでは、 result_jpresult_en がデコレータです。これらのデコレータは、実行結果を日本語または英語で表示するように関数を変更します。

def add_int(a, b):
    return a + b

def result_jp(func):
    def _print_jp(*args, **kwargs):
        print("答えは ",  func(*args, **kwargs), "です。")
    return _print_jp

def result_en(func):
    def _print_en(*args, **kwargs):
        print("The answer is ",  func(*args, **kwargs))
    return _print_en
    
if __name__ == '__main__':
    add_int(2, 3)

このまま、このプログラムを実行すると、答えは5となります。

>>> python addition.py
5

ここで、デコレートしたい関数 add_int の直前に @decorator を追加すると add_int の中身は変えずに挙動を変えることが出来ます。

@result_jp
def add_int(a, b):
以下略

>>> python addition.py
答えは 5 です。

デコレータを変更するだけで挙動を変えることが出来ます。

@result_en
def add_int(a, b):
以下略

>>> python addition.py
The answer is 5

How to use decorator?

上の例は、私がデコレータの説明のために用意した簡単なものでした。上のような簡単な関数では、わざわざデコレータを用意しなくとも目的の関数自身を書き換えた方が簡単と感じるかもしれません。そのため、もう少し実用的なデコレータをか紹介します。

デコレータによるデバック文の追加

Bill Lubanovie 著 『入門 Python 3』(オライリー・ジャパン発行) に非常に良い例が紹介されています。ここでは、ある関数の引数として何が渡されたのかを確認するデバック文を紹介しています。

def document_it(func):
    def new_function(*args, **kwargs):
        print('Running function:', func.__name__)
        print('Positional arguments:', args)
        print('Keyword arguments:', kwargs)
        result = func(*args, **kwargs)
        print('Result:', result)
        return result
    return new_function

この document_it をデコレータとして使用すると、以下のように関数の引数などの情報がまとめて表示されます。

@document_it
def add_int(a, b):
    return a + b

>>> add_ints(3, 5)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 8
8

まとめ

デコレータは関数に機能を追加するために使われる関数です。具体的には、一時的に作成した関数の挙動を確認するなどのデバックの際に使用されたり、何度も同じ機能が関数内で使用される時にDRY原則を守るために使用されたり、巨大になった関数の機能を分割するためになど、様々な使われ方がされています。