Python のデコレータ式 (2) のつづき
1. デコレートされる関数に引数がある場合を考える
これまで試したデコレータ式は、デコレートされる側の関数が引数を取らなかった。
def D(f): print u"デコレータが実行された" return f # デコレータを適用したときに、関数 D が適用される。 @D def hoge(): print "hoge" hoge()
前回は `デコレートする側に引数がある’ 場合について考えた。
def D(arg): def _(f): def __(): print "*--" * arg f() print "--*" * arg return __ return _ @D(5) def hoge(): print "hoge" hoge()
今回は、デコレートされる関数に引数がある場合を考える。
2. デコレータが受け取る関数の引数を固定しないために、可変引数を使う
デコレートする関数は、デコレートされる関数の引数について予め分からない。どのような型の値が渡されるか未知であり、関数が引数をいくつとるのか知らない。
もし、デコレータの受け取る関数の引数の数が固定されていたら、デコレータは使いにくい。これに対処するには、可変引数を使う。
デコレータについて考える前に、Python の可変引数 の書き方を復習しておく。
def test(*args): for e in args: print e test(1,2,3,"hoge","piyo") def test2(**keywords): for k,v in keywords.iteritems(): print k, v test2(a=100, b=200)
Python では、引数の前にアスタリスクを付けると、可変引数となる。
*args, **keywords
3. デコレートされる関数の引数が一つの場合
最初は、「デコレートされる側の関数の引数が一つ」の場合について考える。
def D(f): def _(arg): print "*--" * 10 f(arg) print "--*" * 10 return _ @D def hoge(n): print "hoge" * n ##hoge = D(hoge) hoge(3)
これを実行すると、
*--*--*--*--*--*--*--*--*--*-- hogehogehoge --*--*--*--*--*--*--*--*—*—*
コードが実行される様子を順に考える。上記コード中のコメントアウトしてある、デコレータ式を使わない場合で考えると分かりやすい。
- デコレータ関数 D に、関数 hoge を渡すと、`_ 関数’ が返される。このとき引数 f に hoge がセットされる。
- hoge(3) の呼出しは、`_(3)’ となり、ネストされた関数が実行される。
追記 (2009.12.22) : 上記の手順 1 で返される `_ 関数’ は、
引数を一つ受け取る関数
として定義されている。デコレートしたときに、何か特別な機能として arg に引数が渡される仕組みがあるわけではない。
4. デコレートされる関数の引数が複数の場合
次に、デコレートされる関数の引数が、複数の場合に対応できるように変更する。
def D(f): def _(*args): print "*--" * 10 f(*args) print "--*" * 10 return _ @D def hoge(n): print "hoge" * n @D def piyo(a,b): print "piyo" * a print "piyo" * b ##hoge = D(hoge) ##piyo = D(piyo) hoge(3) piyo(3,5)
実行した結果は、
*--*--*--*--*--*--*--*--*--*-- hogehogehoge --*--*--*--*--*--*--*--*--*--* *--*--*--*--*--*--*--*--*--*-- piyopiyopiyo piyopiyopiyopiyopiyo --*--*--*--*--*--*--*--*--*--*
デコレートする関数におけるネストした関数の引数を
*args
とすることにより、引数の数が異なる hoge(n), piyo(a,b) 関数をデコレートできるようになった。
`_ 関数’ は、
複数の引数を受け取る関数
として定義されている。
5. デコレートされる関数の引数にキーワード引数が含まれる場合
デコレートされる関数の引数に、キーワード引数 が含まれている場合は、次のように記述する。
def D(f): def _(*args, **keywords): print "*--" * 10 f(*args, **keywords) print "--*" * 10 return _ @D def hoge(n): print "hoge" * n @D def piyo(x,y): print "piyo" * x print "piyo" * y @D def fuga(a,b,c=8,d=10): print "fuga" * a print "fuga" * b print "fuga" * c print "fuga" * d hoge(5) piyo(5,10) fuga(1,2) fuga(1,2,3) fuga(1,2,c=10) fuga(1,2,c=10,d=5)
これまでと同じように、デコレートされたときに
複数の引数とキーワード引数を受け取る関数
が返されるように定義する。
6. デコレートする関数、デコレートされる関数に引数がある場合
前回は、デコレートする関数に引数がある場合について考えた。
デコレートする関数と、デコレートされる関数に引数がある場合を定義してみる。デコレータに引数が渡されるので、ネストが一つ増えることに注意する。
def D(n): def _(f): def __(*args, **keywords): print "*--" * n f(*args, **keywords) print "--*" * n return __ return _ @D(15) def hoge(n): print "hoge" * n @D(10) def fuga(a,b,c=8,d=10): print "fuga" * a print "fuga" * b print "fuga" * c print "fuga" * d ##hoge = D(15)(hoge) hoge(10) fuga(1,2,c=5,d=7)
実行した結果は、
*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- hogehogehogehogehogehogehogehogehogehoge --*--*--*--*--*--*--*--*--*--*--*--*--*--*--* *--*--*--*--*--*--*--*--*--*-- fuga fugafuga fugafugafugafugafuga fugafugafugafugafugafugafuga --*--*--*--*--*--*--*--*--*—*
7. デコレータを重ねて利用する
最後に、
- デコレートに必要な情報をデコレータの呼出しのときに渡し、
- デコレータを重ねて利用する
と次のようになる。
def D(top, bottom, x, y): def _(f): def __(*args, **keywords): print top * x f(*args, **keywords) print bottom * y return __ return _ @D("-#-", "-#-", 4, 10) @D("--*", "*--", 3, 9) @D("-+-", "+-+", 2, 7) def fuga(a,b,c=8,d=10): print "fuga" * a print "fuga" * b print "fuga" * c print "fuga" * d fuga(1,2,c=3,d=4)
結果は、
-#--#--#--#- --*--*--* -+--+- fuga fugafuga fugafugafuga fugafugafugafuga +-++-++-++-++-++-++-+ *--*--*--*--*--*--*--*--*-- -#--#--#--#--#--#--#—#—#—#-
これで、一つのデコレータで様々な装飾を施せるようなった。 ^^
下図に、色分けして見やすくしたコードを示す。
0コメント:
コメントを投稿