2008年10月17日金曜日

Python のデコレータ式 (3) - デコレートする側とデコレートされる側に引数がある場合

Python のデコレータ式 (2) のつづき

1. デコレートされる関数に引数がある場合を考える

これまで試したデコレータ式は、デコレートされる側の関数が引数を取らなかった。

Python のデコレータ式 (1) より、

def D(f):
    print u"デコレータが実行された"
    return f

# デコレータを適用したときに、関数 D が適用される。
@D
def hoge():
    print "hoge"

hoge()

前回は `デコレートする側に引数がある’ 場合について考えた。

Python のデコレータ式 (2) より、

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
--*--*--*--*--*--*--*--*—*—*

コードが実行される様子を順に考える。上記コード中のコメントアウトしてある、デコレータ式を使わない場合で考えると分かりやすい。

  1. デコレータ関数 D に、関数 hoge を渡すと、`_ 関数’ が返される。このとき引数 f に hoge がセットされる。
  2. 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. デコレータを重ねて利用する

最後に、

  1. デコレートに必要な情報をデコレータの呼出しのときに渡し、
  2. デコレータを重ねて利用する

と次のようになる。

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
+-++-++-++-++-++-++-+
*--*--*--*--*--*--*--*--*--
-#--#--#--#--#--#--#—#—#—#-

これで、一つのデコレータで様々な装飾を施せるようなった。 ^^

下図に、色分けして見やすくしたコードを示す。

081016-001