2014/12/23

Pythonのデコレータの使い方

Pythonのデコレータの使い方について見てみます。

デコレータとは、既存の関数やクラスを「装飾」する機能のこと。デコレータを使うことで、既存の関数やクラスの中身を直接触ることなく、その外側から機能を追加したり書き換えたりすることができます。

Pythonでは、関数やクラスもオブジェクトということもあって、自分オリジナルのデコレータをかんたんに作ることができます。

具体的に見ていきます。次のような hello 関数が定義されているものとします。
# あいさつを返す関数
def hello():
    return "konichiwa"

print hello()  # => konichiwa
# 定義したとおりの結果が返ってくる

続いて、 mydec 関数を定義し hello 関数を書き換えます。
# デコレータパターンを使うために
# 関数を受け取り関数を返す関数を定義する
def mydec(func):
    def new_func():
        print "%s function called".format(func.__name__)
        return func()
    return new_func

# hello を書き換え
hello = mydec(hello)

print hello()
# => hello function called
#    konichiwa
書き換えた結果、新たに作られた hello は元の hello と同じ戻り値を保ちながら、標準出力に hello function called と出力するものに変わりました。

すでにある関数を装飾するこの一連の処理がデコレータパターンです。キモとなっているのは hello = mydec(hello) の一文です。

Pythonではこの一文は @ を使ったシンタックスシュガーでシンプルな形に書き換えることができます。これがいわゆるデコレータ構文です。
@mydec
def hello():
    return "konichiwa"
このコードは次のコードと等価です。
def hello():
    return "konichiwa"
hello = mydec(hello)

「@デコレータ名」をいくつも積み重ねて、複数のデコレーションを行うことも可能です。


上記の hello は引数がない場合でしたが、引数がある関数に適用できるデコレータも、可変長引数などを使ってかんたんに定義ことができます。
def mydec(func):
    def new_func(*args, **kwds):
        print “%s function called”.format(func.__name__)
        return func(*args, **kwds)
    return new_func

@mydec
def some_function(*args):
    # ... content

ただし、このやり方では、関数のアトリビュート __name__ や __doc__ が書き換えられてしまうという問題があります。

この書き換え問題を手軽に解決してくれるのが、標準ライブラリの functools.wraps です。この wraps デコレータを使えば、書き換え前の関数のアトリビュートを書き換え後のものに手軽に移すことができます。
from functools import wraps

def memoize(func):
    cache = {}
    # wraps を使って元の関数のアトリビュート等を書き換え後の関数にコピーする
    @wraps(func)
    def decorated_func(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return decorated_func

@memoize
def some_function(*args):
    # ... content

一見ややこしいのですが、 wraps は次のような形で使うデコレータです。
@wraps(元の関数)
def 書き換え後の新しい関数():
    # 新しい関数の中身
よくよく考えればこの書き方になるのも納得ですが、このあたりはもう定型パターンということで、難しく考えずに「こう書くもの」と捉えてしまってもよいかもしれません。


以上です。


ちなみに、@構文は関数だけでなくクラスに対しても適用することができます。また、デレコータそのものも関数ではなくクラスとして定義することなんかも可能です。

もっと詳しく見てみたい場合は、わかりやすい解説をしている方がたくさんいらっしゃるので参考ページを参照してみてください。

参考
英語ですがとってもわかりやすいです。
Decorators and Functional Python

いろんなデコレータパターンを紹介してあります。
PythonDecoratorLibrary - Python Wiki

日本語で書かれている解説ではこのあたりのページがおすすめです。
Python - デコレータ
Pythonのデコレータを理解するときに残したメモ - kk6のメモ帳*

公式ドキュメントの解説。ちょっと短すぎるような。。。
functools.wraps — Python公式ドキュメント

0 件のコメント: