エンジニアリングとお金の話

都内で働くエンジニアの日記です。

【技術】pythonのデコレータを整理してみた

【SPONSORED LINK】

pythonのデコレータが良く分かっていなかったので自分なりに整理してみた。

デコレータとは?

関数を関数で包む為の簡単な書き方(シンタックスシュガー)。具体的には関数hogeの前に@decoと付けるとhoge=deco(hoge)となる。

#デコレータ関数
def deco(func):
    def _deco():
        print "start"
        func()
        print "end"
    return _deco

#デコレータ対象関数
@deco
def hoge():
    print "hoge"

hoge()

#実行結果
start
hoge
end

上記の場合、関数hogeが関数decoにラッピングされた形で実行される。これは@decoを関数hogeの前に付ける事により、関数hogeが関数deco(hoge)に置き換えられる為である。本来であればhoge=deco(hoge)と書かなければならないところをデコレータを使用する事で@decoを関数の前に書くだけで同じ動きをする事となる。

なお、デコレータ関数はデコレータ対象関数を引数として受け取る。その為、必ず引数が受け取れる形でなければならない。また、デコレータ関数の戻り値をデコレータ対象関数へ代入する為、デコレータ関数の戻り値は必ず関数でなければならない。(文字列を返してもいいが関数としては実行出来ない。また戻り値を返さない場合はデコレータ対象関数が無くなる為実行出来ない。)

※デコレータ関数にて引数を受け取れない場合

#デコレータ関数(引数無し)
def deco():
    def _deco():
        print "start"
        func()
        print "end"
    return _deco

#デコレータ対象関数
@deco
def hoge():
    print "hoge"

hoge()

#実行結果 ※関数decoが引数を受け取れない為エラーとなる。
Traceback (most recent call last):
  File "D:\work\test\decotest.py", line 217, in <module>
    @deco
TypeError: deco() takes no arguments (1 given)

※デコレータ関数にて戻り値を返却しない場合

#デコレータ関数
def deco(func):
    def _deco():
        print "start"
        func()
        print "end"
  #return _deco  戻り値を削除

#デコレータ対象関数
@deco
def hoge():
    print "hoge"

hoge()

#実行結果 ※関数hogeが存在しない為エラーとなる
Traceback (most recent call last):
  File "D:\work\test\decotest.py", line 220, in <module>
    hoge()
TypeError: 'NoneType' object is not callable

デコレータのパターン

多分これぐらい覚えれば何とかなると思われる。

1.基本的なパターン
 シンタックスシュガー:hoge=deco(hoge)

#デコレータ関数
def deco(func):
    def _deco():
        print "start"
        func()
        print "end"
    return _deco

#デコレータ対象関数
@deco
def hoge():
    print "hoge"

hoge()

#実行結果
start
hoge
end

2.デコレータ対象関数が引数を持つパターン
 シンタックスシュガー:hoge=deco(hoge)

#デコレータ関数
def deco(func):
    def _deco(*args,**kwargs): #引数を受け取れる様にする
        print "start"
        func(*args,**kwargs)  #引数を渡す
        print "end"
    return _deco

#デコレータ対象関数
@deco
def hoge(arg):
    print "hoge" + arg

hoge("HOGE")

#実行結果
start
hogeHOGE
end

3.デコレータ関数が引数を持つパターン
 シンタックスシュガー:hoge=deco(arg)(hoge)

#デコレータ関数
def deco(arg):       #引数を受け取れる様にする("HOGE"を受け取る)
    def _deco(func): #引数を受け取れる様にする(関数hogeを受け取る) 
        def __deco():
            print "start"
            print arg
            func()
            print "end"
        return __deco
    return _deco

#デコレータ対象関数
@deco("HOGE")
def hoge():
    print "hoge"

hoge()

#実行結果
start
HOGE
hoge
end

4.デコレータ関数が複数パターン
 シンタックスシュガー:hoge=deco1(deco2(hoge))

#デコレータ関数1
def deco1(func):
    def _deco1():
        print "start deco1"
        func()
        print "end deco1"
    return _deco1

#デコレータ関数2
def deco2(func):
    def _deco2():
        print "start deco2"
        func()
        print "end deco2"
    return _deco2

#デコレータ対象関数
@deco1
@deco2
def hoge():
    print "hoge"

hoge()

#実行結果
start deco1
start deco2
hoge
end deco2
end deco1

5.デコレータ関数がクラスパターン1
 シンタックスシュガー:hoge=Deco(hoge))

#デコレータ関数用クラス
class Deco():
    def __init__(self,func):
        self.func=func

    #関数として使用出来る様に__call__を使用
    def __call__(self):
        print "start"
        self.func()
        print "end"

@Deco
def hoge():
    print "hoge"

hoge()

#実行結果
start
hoge
end

6.デコレータ関数がクラスパターン2
 シンタックスシュガー:hoge=deco.deco(hoge)

#デコレータ関数用クラス
class Deco():
    def deco(self,func):
        def _deco():
            print "start"
            func()
            print "end"
        return _deco

deco=Deco()

@deco.deco
def hoge():
    print "hoge"

hoge()

#実行結果
start
hoge
end

まとめ

デコレータを一番手っ取り早く理解する方法はシンタックスシュガーを覚える事だと思う。考えるな、感じろ!がデコレータ理解のポイント。