2016/06/17更新

[Python] デコレーターに入門する

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
Pythonのコードで時々ある@xxxというアノテーションがついた関数がありますが、そのアノテーションをデコレータと言います。今日はその使い方や定義方法などブログに書きたいと思います。

画像

目次




デコレーターとは

デコレーターとは、以下のように関数に@xxxをつけるものを指します。
@document_it
def add_ints(a, b):
    return a + b
上記の@document_itがデコレータで、これを付与することでadd_ints関数の中身を変えずして振る舞いを変えることができます。FlaskなどのWebアプリケーションでは、@app.route('/')などでルーティングにもちいられています。

このブログでは、デコレータの定義方法や使い方を書きたいと思います。



デコレーターを定義する

デコレーターに利用する関数は、以下のように定義することができます。
# デコレーター用の関数
# 引数に、デコレータ対象の関数を受け取る
def document_it(func):
    """デコレータ対象の関数について、関数名 / 引数の内容 / 実行結果を表示する"""
    def new_function(*args, **kwargs):
        # デコレータ付与先の関数名
        print("Runnning 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
def add_ints(a, b):
    return a + b
これで、デコレーターの設定は完了です。実際に動かしてみると、以下のように表示されます。
>>> add_ints(1, 2)
Runnning function: add_ints
Positional arguments: (1, 2)
Keyword arguments: {}
Result: 3
3
このように関数の中身を変えずに、処理を追加/変更できるのがデコレーターの特徴です。なかなか便利ですね!

また、以下のデコレーターのように付与対象の関数の結果を変化させることもできます。
# デコレート対象の関数の結果を二乗する
def square_it(func):
    def square_it_new_function(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * result
    return square_it_new_function

@square_it
def add_ints(a, b):
    return a + b

# 実行結果が二乗される
add_ints(1, 2) # 9



複数のデコレーターを付与した場合の実行順序

デコレーターは複数付与することもできます。その場合には、関数に最も近いデコレーターから実行されます。
# 実行順を見える化するために、printを各所に追加
def document_it(func):
    def new_function(*args, **kwargs):
        print("document_it starts")
        print("Runnning function:", func.__name__)
        print("Positional arguments:", args)
        print("Keyword arguments:", kwargs)
        result = func(*args, **kwargs)
        print("Result:", result)
        print("document_it ends")
        return result
    return new_function

# 同じく、printを追加
def square_it(func):
    # 関数名もdocument_itを変えています
    def square_it_new_function(*args, **kwargs):
        print("square_it starts")
        result = func(*args, **kwargs)
        print("square_it ends")
        return result * result
    return new_function

# デコレーターを2つ付与
@document_it
@square_it
def add_ints(a, b):
    return a + b

# 実行した結果は以下の通り
add_ints(1, 2)
"""
document_it starts
# document_itが受け取る関数は、square_it_new_functionとなっている
Runnning function: square_it_new_function
Positional arguments: (1, 2)
Keyword arguments: {}
# 関数の処理としては、squre_itから実行される
square_it starts
square_it ends
Result: 9
document_it ends
9
"""

# デコレーターを逆順にしてみると、実行順序が変わる
@square_it
@document_it
def add_ints(a, b):
    return a + b

# 実行した結果は、以下
add_ints(1, 2)
"""
square_it starts
document_it starts
# 今回の場合は、document_itデコレーターはadd_intsを関数として受け取っている
Runnning function: add_ints
Positional arguments: (1, 2)
Keyword arguments: {}
Result: 3
document_it ends
square_it ends
9
"""
このように、複数のデコレーターを付与した場合には、関数に近い順から実行されます。



参考資料

Pythonの基礎勉強は、以下の書籍を熟読して猛威勉強中です。色々と掲載されていて非常に参考になります。

『入門 Python 3(O'Reilly)』



最後に

今日はPythonの@xxxであるデコレーターをブログで扱いました。Pythonで最初にこれを見たときに、なんだろうこれと思っていたのを思い出しますw。関数の中身を変えずして振る舞いを変えられるのは、なかなか素敵ですね♩

最後になりますが本ブログでは、Python・Swift・Java・フロントエンド・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。