承前:Pythonのデコレータ(decorator)を理解する 1
デコレータ詳説
先の例に対して、デコレータ構文を使うと次のようになる:
@my_shiny_new_decorator def another_stand_alone_function(): print "Leave me alone" another_stand_alone_function() #出力: #Before the function runs #Leave me alone #After the function runs
そう、これで全部、シンプルだ。@decoratorは単なる、
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
のショートカットにすぎない。Decoratorはデザインパターンにおけるwikipedia:Decorator パターンのPythonicな変形にすぎないのだ。Pythonにはiteratorのように開発を容易にするための古典的デザインパターンがいくつか組み込まれている。
当然だが、デコレータは積み重ねることができる。
def bread(func): def wrapper(): print "</''''''\>" func() print "<\______/>" return wrapper def ingredients(func): def wrapper(): print "#tomatoes#" func() print "~salad~" return wrapper def sandwich(food="--ham--"): print food sandwich() # => --ham-- sandwich = bread(ingredients(sandwich)) sandwich() #出力: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/>
Pythonのデコレータ構文を使うと次のようになる:
@bread @ingredients def sandwich(food="--ham--"): print food sandwich() #出力: #</''''''\> # #tomatoes# # --ham-- # ~salad~ #<\______/>
(デコレータを書く)順序はデコレータが何であるかを決定する。
@ingredients @bread def strange_sandwich(food="--ham--"): print food strange_sandwich() #出力: ##tomatoes# #</''''''\> # --ham-- #<\______/> # ~salad~
結局のところ質問に答えると
結論として、あなたはどのようにこのStackOverFlow.comの質問(最初の質問)に答えればいいか簡単に分かるでしょう。
# ボールド(太字)にするためのデコレータ def makebold(fn): # デコレータを返す新しい関数 def wrapper(): # 前後にいくつかのコードを挿入する return "<b>" + fn() + "</b>" return wrapper # イタリックにするためのデコレータ def makeitalic(fn): # デコレータを返す新しい関数 def wrapper(): # 前後にいくつかのコードを挿入する return "<i>" + fn() + "</i>" return wrapper @makebold @makeitalic def say(): return "hello" print say() # => <b><i>hello</i></b> # これは実際には次に等しい def say(): return "hello" say = makebold(makeitalic(say)) print say() # => <b><i>hello</i></b>
これであなたは幸せのうちにここを去ることもできますが、少し知恵を絞ることによって応用的なデコレータの利用法を知ることもできます。
デコレートされた関数に引数を渡す
# これは黒魔術ではない、あなたのラッパーは単に引数を # 渡すことができなければならない: def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): print "I got args! Look:", arg1, arg2 function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments # デコレータによって返された関数を呼び出している時点で # 引数を渡すと、ラッパーを呼び出してそれがデコレートされた # 関数に引数を渡すようになる @a_decorator_passing_arguments def print_full_name(first_name, last_name): print "My name is", first_name, last_name print_full_name("Peter", "Venkman") # 出力: #I got args! Look: Peter Venkman #My name is Peter Venkman
メソッドをデコレートする
Pythonの素晴らしいところは、メソッドの第一引数が現在のオブジェクト(self)への参照を持つことを期待されているという点を除けば、メソッドと関数が実際には同じものであるということだ。それはメソッドが(関数と)同じ方法でデコレータを構築できるということを意味するが、selfを取るという点を忘れずに覚えておこう。
def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 # とても親切だ、勝手にサバを読んでくれる :-) return method_to_decorate(self, lie) return wrapper class Lucy(object): def __init__(self): self.age = 32 @method_friendly_decorator def sayYourAge(self, lie): print "I am %s, what did you think?" % (self.age + lie) l = Lucy() l.sayYourAge(-3) # => I am 26, what did you think?
当たり前だが、あなたが極めて汎用的なデコレータを作って任意の関数やメソッドに適用することを望む場合は、引数の数に依存しない、*argsや**kwargsを利用するとよいだろう。
def a_decorator_passing_arbitrary_arguments(function_to_decorate): # ラッパーは任意の引数を受け入れる def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print "Do I have args?:" print args print kwargs # ここでは引数*args, **kwargsを展開している。 # 展開に慣れていない方は以下のURLを参照: # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) return a_wrapper_accepting_arbitrary_arguments @a_decorator_passing_arbitrary_arguments def function_with_no_argument(): print "Python is cool, no argument here." function_with_no_argument() #出力 #Do I have args?: #() #{} #Python is cool, no argument here. @a_decorator_passing_arbitrary_arguments def function_with_arguments(a, b, c): print a, b, c function_with_arguments(1,2,3) #出力 #Do I have args?: #(1, 2, 3) #{} #1 2 3 @a_decorator_passing_arbitrary_arguments def function_with_named_arguments(a, b, c, platypus="Why not ?"): print "Do %s, %s and %s like platipus? %s" %\ (a, b, c, platypus) function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!") #出力 #Do I have args ? : #('Bill', 'Linus', 'Steve') #{'platypus': 'Indeed!'} #Do Bill, Linus and Steve like platipus? Indeed! class Mary(object): def __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge(self, lie=-3): # You can now add a default value print "I am %s, what did you think ?" % (self.age + lie) m = Mary() m.sayYourAge() #出力 # Do I have args?: #(<__main__.Mary object at 0xb7d303ac>,) #{} #I am 28, what did you think?