1. デコレータの仕組みについて知りたい
Python を使っていて、これまでに所々でデコレータを目にしてきた。
デコレータの仕組みについて理解してないので、そろそろ確認しておくことにする。
デコレータと等価な関数定義
7.5 関数定義 には、デコレータを使った関数定義と、それと同等の式の例が書かれている。
関数定義は一つまたは複数のデコレータ式 (decorator expression) でラップできます。…
例えば、以下のようなコード:
@f1(arg) @f2 def func(): passは、
def func(): pass func = f1(arg)(f2(func))と同じです。
これを元に、いくつか例を考えてみる。
2. デコレータを適用したときに文が実行される、何もしないデコレータ
a. デコレータ式を使わない場合
次の関数を定義する。
- 文字列 “hoge” を出力する、関数 hoge を定義。
- 関数を引数として受け取る、関数 D を定義。この関数は、関数を受け取ると、print 文を実行する他は何もせず、呼び出されたときに与えられた関数を、そのまま返す。
この 関数 D がデコレータの役割をする。 (これをデコレータと言ってしまうのは、名が体を表わしてないけれど ^^;)
def D(f): print u"デコレータが実行された" return f def hoge(): print "hoge" # hoge 関数をデコレート hoge = D(hoge) hoge()
変数 hoge に、関数 hoge にデコレータ関数 D を適用した結果を代入。そして hoge() を実行。結果は、
デコレータが実行された hoge
デコレートする側の関数 D が実行されるタイミングに注目すると、hoge 関数を D に渡したときに実行されているのがわかる。
b. デコレータ式を使う場合
上記は、次のデコレート式を使った場合と同じ。
def D(f): print u"デコレータが実行された" return f # デコレータを適用したときに、関数 D が適用される。 @D def hoge(): print "hoge" hoge()
先ほどと同じく、
@D
で、デコレーターが適用されたとき、デコレータとなる関数 D が実行される。hoge() の呼出しでデコレータとなる関数が実行されるわけではない。
3. ネストした関数を利用し、デコレータを適用しただけでは、何もしないように変更
a. デコレータ式を使わない場合
次に、デコレータを適用した時点で、デコレータ関数が実行されないように変更する。
そのためには、ネストした関数を利用すれば良い。
- cf. 関数のネストと f()()
以下では、デコレータ関数 D の中に、関数 _ を定義した。普通は、適当な名前を付けると思うけれど、見やすいので、便宜的に関数名にアンダーバーを利用した。
def D(f): def _(): print "*--" * 10 f() print "--*" * 10 return _ def hoge(): print "hoge" # デコレータ関数 D を適用する前 print "before decoration: " hoge() # デコレータ関数 D を 関数 hoge に適用 hoge = D(hoge) # デコレータ関数 D を hoge に適用した後 print "after decoration: " hoge()
結果は、
before decoration: hoge after decoration: *--*--*--*--*--*--*--*--*--*-- hoge --*--*--*--*--*--*--*--*--*--*
デコレートする前の hoge() の呼出しでは、hoge 関数の内容が、そのまま出力された。
デコレートした後では、出力に「飾り」がついている。
また、デコレータを適用した時点では、先ほどのように、デコレータ関数の中身が実行されていない。理由は、デコレータ D に渡された関数 hoge は、ネストした関数の中で実行される。しかし、デコレータ関数を hoge 関数に適用したときに返されるのは、ネストした関数 _ であるため、その時点では実行されない。
後の hoge() の呼び出しにより、ネストした関数 _ が実行される。その時点で、デコレートされて hoge 関数が実行される。
もちろん、デコレータなので、関数 D は hoge 関数をデコレートする専用の関数ではない。例えば、上記のコードの下に、次のように piyo 関数を書いて、piyo をデコレートしてみる。
def piyo(): print "piyo" piyo = D(piyo) piyo()
結果、piyo もデコレートされる。
*--*--*--*--*--*--*--*--*--*-- piyo --*--*--*--*--*--*--*--*--*—*
このとき、hoge, piyo をデコレートするネストした関数 _ は別々のオブジェクト。
b. デコレータ式を使う
上記をデコレータ式で書き直すと、
def D(f): def _(): print "*--" * 10 f() print "--*" * 10 return _ @D def hoge(): print "hoge" hoge() @D def piyo(): print "piyo" ##piyo = D(piyo) piyo()
4. デコレータを利用して、本質的な処理と、飾りを分ける
関心の分離
アスペクト指向的な点を強調するなら、
次のような、コードがデコレータの役割を端的に表している。
def D(f): def _(): print u"▲▲▲ 事前処理 ▲▲▲" f() print u"▼▼▼ 事後処理 ▼▼▼" return _ @D def hoge(): print u"■■■" print u"■■■ hoge の本質的な処理" print u"■■■" hoge() @D def piyo(): print u"■■■" print u"■■■ piyo の本質的な処理" print u"■■■" piyo()
結果は、
▲▲▲ 事前処理 ▲▲▲ ■■■ ■■■ piyo の本質的な処理 ■■■ ▼▼▼ 事後処理 ▼▼▼ ▲▲▲ 事前処理 ▲▲▲ ■■■ ■■■ piyo の本質的な処理 ■■■ ▼▼▼ 事後処理 ▼▼▼
ただし、デコレータ式の場合、アスペクトを挿入したい関数の前に記述する必要がある。
Aspect-oriented programming - Wikipedia, the free encyclopedia に列挙されているライブラリでは、その辺りが柔軟に操作できそう。
5. デコレートする関数の中で、関数のプロパティを利用する
関数の特殊属性を使えば、デコレータの中で、関数の名前などのプロパティを取得できる。
- cf. 関数のプロパティ
def D(f): def _(): print "before: " + f.__name__ + "()" f() print "after: " + f.__name__ + "()" return _ @D def hoge(): print "hoge" hoge()
Python のデコレータ式 (2) につづく…
関連サイト
を知っていれば、デコレータ式は、その関数版で、シンプルな記法により、お手軽に利用できるツ-ル、と言った感じかな。
0コメント:
コメントを投稿