Pythonでデコレータの引数を元に関数を動的に呼び出すアレについて。

bottleとかflaskなどのPythonのフレームワークでは、Pythonのデコレータという機能を使って、URLのパターンを定義している。

from bottle import route, run, template

@route('/hello')
def index():
    return template('<b>Hello</b>!')

run(host='localhost', port=8080)

たとえばbottleならば上のコードだと/helloというページにアクセスすると
index関数が実行される。
@routeの部分がデコレータだけれども、デコレータで指定した文字列が関数を呼ぶイベントを定義しているみたいだ。

そもそもデコレータとは

 

Pythonに備わっている機能。
ある関数AにデコレータBをつけた場合、関数Aの実行前後にデコレータBで書いた処理が実行される。
関数の前後で共通の処理がある場合は便利だ。

デコレータ自体は普通の関数と変わらない。
お決まりの書き方で書くことによってデコレータになる。

あまりどのような仕組みなのか考えたことがなかったので、
サンプルコードを書いてみた。



def decofun(hikisu):
    print("#####デコレータの中 : "+ hikisu +" #####")
    def deco1(func):
        print("#####デコレータの中の中 #####")
        def deco2(*args , **kwargs):
            print("#####デコレータの中の中の中 #####")
            result = func(*args , **kwargs)
            return result
        return deco2
    return deco1

print("##### hoge関数と最初のデコレータが読み込まれる #####")
@decofun('りんご')
def hoge(nanika):
    print("hogeだよー :" + nanika)

print("##### foo関数と最初のデコレータが読み込まれる #####")
@decofun('みかん')
def foo(nanika):
    print("fooだよー :" + nanika)

print("")
print("##### 関数たちを実行 #####")
if __name__ == '__main__':
    foo("ふぉお")
    hoge("ほげー")

関数名、変数名は適当ですまない。

decofunという関数がデコレータだ。
デコレータは装飾される対象(サンプルだとhoge関数とかfoo関数)を引数として受け取って、それを実行する関数を返す。2段になっている
pythonは関数は引数や戻り値として扱えるのだ。

今回のサンプルはデコレータが引数を受けるパターン、
この場合は書き方は少し複雑になって、デコレータに書かれた引数を受け、対象の関数(hogeとか)を引数で受ける関数、hogeなどを実行する関数と普通より1段増えている。

さあこれを実行したらどうなるか…

##### hoge関数と最初のデコレータが読み込まれる #####
#####デコレータの中 : りんご #####
#####デコレータの中の中 #####
##### foo関数と最初のデコレータが読み込まれる #####
#####デコレータの中 : みかん #####
#####デコレータの中の中 #####

##### 関数たちを実行 #####
#####デコレータの中の中の中 #####
fooだよー :ふぉお
#####デコレータの中の中の中 #####
hogeだよー :ほげー

ソース内のprintと結果を見比べてほしい。
@decofunの行が読み込まれた時点で、decofun関数が呼ばれている。
foo関数やhoge関数が呼ばれた時点で、decofun関数の中にあるdeco2関数が呼ばれている。
foo関数やhoge関数は、そのときに戻り値で返ってきたdeco2関数に書き換えられているのだと思う。
てかこれ、所謂クロージャだ。
hogeやfooは、deco2に書き換えられているけれども、deco2の外側のスコープにあるfunc変数(hogeやfooの関数オブジェクトが入っている)が参照できるので、挙動が違う関数が作られているんだ。

そっか、クロージャってこういう活用方法があったんだー

動的に関数を呼び出すアレ

bottleなどのデコレータに文字列食わせて、その文字列をキーにして関数を動的に呼ぶ方法もこの仕組みが分かればどうやっているのかわかってきたぞ

つまりこういうことだ。


funcdic = {}

def decofun(hikisu):
    print("#####デコレータの中 : "+ hikisu +" #####")
    def deco1(func):
        print("#####デコレータの中の中 #####")
        def deco2(*args , **kwargs):
            print("#####デコレータの中の中の中 #####")
            result = func(*args , **kwargs)
            return result
        funcdic[hikisu] = deco2
        return deco2
    return deco1

print("##### hoge関数と最初のデコレータが読み込まれる #####")
@decofun('りんご')
def hoge(nanika):
    print("hogeだよー :" + nanika)

print("##### foo関数と最初のデコレータが読み込まれる #####")
@decofun('みかん')
def foo(nanika):
    print("fooだよー :" + nanika)

print("")
print("##### 関数たちを実行 #####")
if __name__ == '__main__':
    foo("ふぉお")
    hoge("ほげー")

    print("")
    print("#### 動的に呼び出す ####")
    funcdic["りんご"]("ぐごー")
    funcdic["みかん"]("がおー")

fooやhogeはdeco2に置き換わっているのだから、
デコレータに渡された文字列をキーにdeco2を辞書に放り投げればいい。

サンプルの最後の二行を見るのだ。
ここのりんごやみかんの部分を、動的に受ければ
こういう文字列が来た時にはこういう関数が呼ばれるというような、一種のイベントハンドラー的な動作がつくれる。

実行結果も載せておく。

##### hoge関数と最初のデコレータが読み込まれる #####
#####デコレータの中 : りんご #####
#####デコレータの中の中 #####
##### foo関数と最初のデコレータが読み込まれる #####
#####デコレータの中 : みかん #####
#####デコレータの中の中 #####

##### 関数たちを実行 #####
#####デコレータの中の中の中 #####
fooだよー :ふぉお
#####デコレータの中の中の中 #####
hogeだよー :ほげー

#### 動的に呼び出す ####
#####デコレータの中の中の中 #####
hogeだよー :ぐごー
#####デコレータの中の中の中 #####
fooだよー :がおー

普通にfooやhogeを実行しているのと同じ動作になっている。

クロージャという仕組みはとっつきにくく、
イマイチ実用性が理解できない技法の一つだが、
このデコレータの仕組みを追っていくことで、理解や有用性がわかるかもしれない。

あと、Pythonが用意したデコレータの文法を使わず、
単純なクロージャとして実装していけば普通にデコレータと同じ動作を作ることができるな。

スポンサードリンク

関連コンテンツ