デコレータについて(デコレータを引数にしてデコレータを返す関数の作成)
デコレータを引数に、デコレータを返す関数を作って無かったので作ってみる。
今回は、関数用のデコレータをメソッド用のデコレータに変換する関数を作ってみる。
デコレータ
デコレータは以下のようなコードの構文糖衣。
@deco def f(n): return n def g(n): return n g = deco(g) ## 関数が無名なら以下でも同じ。 h = deco(lambda n : n)
デコレータの種類。恣意的なので分類はてきとー。
- 対象
- クラス
- 関数
- メソッド
- 作成する方法
- 関数
- クラス
- callable object(__call__)
- class method (static method)
と色々ある。だいたいのものは以前作ってみたことがある。
でも、そういえば、「デコレータを引数として取って、デコレータを返す関数」を作ったことが無かった。
なので作ってみることにする。
デコレータを引数として取って、デコレータを返す関数の作成
例. 関数用のデコレータをメソッド用のデコレータにする。
関数用デコレータのデコレータを直接メソッドに利用できない。
関数へのデコレータincを作成。デコレータを掛けた関数が、渡された引数に+1した値を利用するように変更するデコレータ。
def inc(f): return lambda n: f(n+1) sq = inc(lambda x : x * x) #これはデコレータを掛けたのと同じこと print sq(10) # 121
作ったincをメソッドにも適用しようとすると失敗する。まあ、当然。引数違うので。
class A(object): @inc def double(self, n): return n * 2 A().double(10) # TypeError: <lambda>() takes exactly 1 argument (2 given)
上手く動作させるには、引数を合わせて、メソッド用にデコレータを作ってあげる必要がある。
def method_inc(method): return lambda self, n: method(self, n+1) class A(object): @method_inc def double(self, n): return n * 2 print A().double(10) # 22
実際のところ、メソッドに対応させると言っても、修正は単にselfを加えるだけ。
関数呼び出し、メソッド呼び出しをまとめると以下のような感じ。
関数 | lambda *args, **kw |
インスタンスメソッド | lambda self, *args, **kw |
*args,**kwは引数全部ということ。selfはインスタンスメソッドを想定しているから。
もちろん、ひとつ引数が増えるのはクラスメソッドについても同様。(lambda cls, *args, **kwになる)
関数用のデコレータをメソッド用のデコレータにする。
いくら簡単と言っても、
- 関数用のデコレータ
- メソッド用のデコレータ
をいちいち2つ作らなければいけないのは面倒だし煩雑。
デコレータを変換する関数があれば良さそう。
具体的には、関数用のデコレータを引数として取って、メソッド用のデコレータを返す関数coerce_methodを作りたい。
実際に利用するときにはこんな感じで使うことを想定している。
class A(object): @coerce_method(inc) def double(self, n): return n * 2
実際に作ってみる。
初めはデコレータを取る、そのデコレータは関数で、これを変換してメソッド用に。
def coerce_method(fn_deco): """fn_decoは関数デコレータ これをメソッドデコレータにして返す """ def method_deco(method): """ 返されるものはメソッド用のデコレータの予定。当然、メソッドを引数として取る。 """ def _method_deco(self, *margs, **mkw): """ メソッド(instance method)呼び出しなので、第一引数はself。 """ @fn_deco def _function(*args, **kw): """ でも、渡されたデコレータは関数用なので対応するために内部で関数を作る。 (selfは外側の関数_method_decoのクロージャがあるので参照可能) """ return method(self, *args, **kw) return _function(*margs, **mkw) return _method_deco return method_deco
できた。上手く動いている。
class B(object): @coerce_method(inc) def square(self, n): return n * n print B().square(10) # 121
docstringのこととか考えるとこのままじゃまずいのだけれど。