Pythonのデコレータ構文について

Djangoのキャッシュに関するドキュメントを読んでいて、(Python2.4から)デコレータ構文というものが使えるらしいと知りました。

関数・メソッドのデコレータ :: Python 2.4 クイックリファレンス
関数にデコレータを適用する『構文』については、上記リンク先の説明を読むだけで理解出来ます。デコレート対象の関数を作成する直前の行に@を頭につけたデコレータ関数名を記述するだけ、と。デコレータ構文の効果は、関数を関数でラップ出来るよ、という事でしょう。

そこで、実際に適当なコードを書いてみようと思ったのですが、デコレータが(内部的に)どういう動作をするのかがいまいち掴めませんでした。特に、引数をとる関数に対するデコレータを書く場合、実行時に渡された引数をどう引き継ぐのかに困りました。

ITmedia エンタープライズ:2.4への機能強化で広がるPythonの世界 (3/4)
上記リンク先のリスト3のコードを参考にコードを書いてみて、ようやく(それっぽい)解釈が出来ました。

# -*- coding: utf-8 -*-
def decorator(*deco_args):
    # デコレート対象とラッパー関数とを結びつける
    # ここでの引数fはデコレート対象の関数が実行されたときの暗黙的な第1引数
    def director(f):
        # デコレート対象の関数の実体を置き換える関数
        def wrapper(*args, **kwargs):
            print "in wrapper", deco_args
            return f(*args, **kwargs)
        # デコレート対象の関数と置き換えの関数とが同じ実体となるように結びつける
        # ここでデコレート対象の関数が実行されたときの引数を引き継ぐようになる
        wrapper.func_name = f.func_name
        return wrapper
    return director
 
@decorator(1, 2, 3)
def func(x, y):
    return x * y
 
print func(3, 5)
print func(4, 5)
 
$ python sample_decorator.py
in wrapper (1, 2, 3)
15
in wrapper (1, 2, 3)
20

まず、デコレータ関数は関数を返します。これは、同等の記述として説明されている"f = D(f, ...)"を読めば分かります。問題は、デコレータ関数に与える引数と、デコレート対象の関数に与える引数の扱いに関してです。

デコレータ関数自身は、デコレータ関数とデコレート対象の関数とが結びつけられる際に1度だけ実行されるようです。さらに、デコレータ関数自身が実行される際の引数は、デコレート構文による記述の際に渡された引数になります。上記のコードでは"1, 2, 3 => *deco_args"となります。

また、上記のコードでは、デコレータ関数が呼び出されると、内部で2つの関数を作成し、最終的にその関数を返しています。外側の関数directorは、デコレート対象の関数が実行された際に、暗黙的に渡される(と思っています)デコレート対象の関数を受け取ります。directorの中で作成しているwrapper関数が、デコレート対象の関数の実体となります。wrapper関数はデコレート対象の関数が実行された時に渡された引数を受け取る必要がありますが、これは何もしないと受け取る事が出来ないようです(受け取り方が分かりません)。そこで、"func_name"という、関数の名前を表す変数をデコレート対象の関数から受け継ぎます。こうする事で、デコレート対象の関数の実体が、wrapper関数になり、実行時に渡された引数も受け取る事が出来るようになりました。

というところまではなんとか(勝手ながらも)解釈できましたが、どうでしょうか。そもそも、『デコレータ構文』という呼び方は正しいのでしょうか?関数修飾子?


余談ですが、IPython使い始めてみました。

プロフィール

このブログ記事について

このページは、koshigoeが2007年2月18日 20:20に書いたブログ記事です。

ひとつ前のブログ記事は「Pythonでスクリーンスクレイピング」です。

次のブログ記事は「Pythonでスクリプトファイル自身のパスを得る」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。