2010年04月10日

引数のカッコが省略可能なデコレータの実装方法


Taken by Chris Gin under CC BY-NC

Pythonの中でも、僕が気に入っている文法がデコレータです。関数やメソッド、クラスの前に置くことで、様々な処理を行うことができます。デコレータを実装するには、ただ関数を書けばいいだけです。実際にコードを書く際にも良く使いますし、このブログでもいくつか紹介してきました。

このエントリーでは、デコレータにまつわるTipsを一つだけお届けします。ちなみにトップ画像の鳥ですが、デコレータ→仮面→masked birdと連想ゲームでした。仮面をかぶったように見える鳥みたいです。

デコレータがいまいちな点

何がいまいちって、引数のカッコの有無で挙動がかなり違う点です。カッコが無い時は、次のようになります。

def deco(func):
    """引数の無いデコレータ"""
    @functools.wraps(func)
    def _decorator_body(arg):
        try:
            # 事前処理
            return func(arg)
        finally:
            # 事前処理
            pass
    return _decorator_body

@deco
def func(arg):
    print "引数無しデコレータ付きの関数です"

# こう解釈される
# func = deco(func)

次は引数有り版です。

def deco(darg):
    """引数ありのデコレータ"""
    def _internal_params(func): 
        @functools.wraps(func)
        def _decorator_body(arg):
            try:
                # 事前処理
                return func(arg)
            finally:
                # 事前処理
                pass
        return _decorator_body
    return _internal_params

@deco("デコレータの引数")
def func(arg):
    print "引数付きデコレータがついた関数です"

# こう解釈される
# func = deco(darg)(func)

ご覧になれば分かるように、引数の有無で階層の数からして違っています。そのため、引数がなかった場合でも、引数を受け取る可能性があるのであれば、デコレータを使用する時にカッコを常に付ける必要があります。

カッコが必要なのに、カッコを忘れたとすると、本来はユーザが定義した関数が入って欲しい所に、_internal_params関数が入ってしまいます。エラーメッセージがきちんと出るわけではなく、実行してみて初めて分かるという、ちょっと想像しにくいバグが発生を誘発しやすくなります。

カッコが省略できるデコレータ記法

それでは、ソースコードを紹介します。

def deco(func_or_arg=None, *dargs, **dkwargs):
    if callable(func_or_arg):
        func = func_or_arg
        @functools.wraps(func)
        def _decorator_body(arg):
            try:
                # 事前処理
                return func(arg)
            finally:
                # 事前処理
                pass
        return _decorator_body
    else:
        dargs = [func_or_arg] + list(dargs)
        def _internal_params(darg): 
            @functools.wraps(func)
            def _decorator_body2(arg):
                try:
                    # 事前処理
                    return func(arg)
                finally:
                    # 事前処理
                    pass
            return _decorator_body2
        return _internal_params

最初の引数が呼び出し可能オブジェクトかどうかで判定しています。もし、デコレータの引数はすべてキーワード引数で指定する、というのであれば、もっと分かりやすいコードになるかもしれません。ちょっと複雑なコードに見えるかもしれませんが、使う側からすれば、カッコの付け忘れで問題が発生することがなくなるため、トラブルに巻き込まれる可能性が減ります。

なお、デコレータの詳細説明や、さまざまな応用例については、Expert Python Programmingという、ステキな書籍で色々紹介されています。現在、鋭意翻訳&レビュー中です。詳細情報についてはしばしお待ちを。Pythonの細かい詳細な文法から、Pythonのパッケージの作り方、アジャイル(テスト、リファクタリング、継続的インテグレーション、プロジェクトの進め方)、ドキュメントの書き方、最適化などなど、とにかく盛りだくさんで、学びの多い本です。Pythonプログラマー以外の人でも気づきが多いと思います。

posted by @shibukawa at 04:16 | Comment(262) | TrackBack(0) | Python はてなブックマーク - 引数のカッコが省略可能なデコレータの実装方法
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/37017128
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック
検索ボックス

Twitter

www.flickr.com
This is a Flickr badge showing public photos and videos from shibukawa.yoshiki. Make your own badge here.
<< 2017年05月 >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
最近の記事
カテゴリ
過去ログ
Powered by さくらのブログ