デコレータはラッパー(関数)であり、ラップした関数それ自体を変更することなしに、
ラップした関数の実行前後のコードをデコレーションできるようにすることを意味します。
デコレータのしくみ
# デコレータは他の関数を引数として期待する関数である
def my_decorator(to_decorate):
# 内部において、デコレータはその場で関数を定義する:ラッパーだ
# この関数は元の関数をラップしようとしている
# よって、それの前と後においてコードを実行することができる。
def my_wrapper():
# ここに元のコードの前に実行したいコードを書く
# 関数が呼び出される
print "Before the function"
# ここで関数を呼ぶ (カッコを使て)
to_decorate()
# ここに元のコードの後に実行したいコードを書く
# 関数が呼び出される
print "After the function"
# この時点ではto_decorateは実行されていない。
# 我々はラッパー関数を作成しただけでそれをreturnする。
# ラッパーには元の関数と前後に実行するコードが含まれている。
# 使う準備は整っている
return my_wrapper
# ここで以下のような二度と触れたくない関数を作成したと想像してみてくれ
def my_function():
print "I am a stand alone function, don't you dare modify me"
my_function()
#出力: I am a stand alone function, don't you dare modify me
# さて、こいつの振る舞いを拡張するためにデコレートすることができる。
# ただデコレータに引き渡すだけで、お望みのコードによって動的にラップし、
# 利用可能な新しい関数を返すのである。
my_function = my_decorator(my_function)
my_function()
#出力:
#Before the function
#I am a stand alone function, don't you dare modify me
#After the function
デコレータ構文
上記コードの呼び出し部分をデコレート構文にすると以下になります。
@my_decorator
def my_function():
print "I am a stand alone function, don't you dare modify me"
my_function()
#出力:
#Before the function
#I am a stand alone function, don't you dare modify me
#After the function
@decoratorは
my_function = my_decorator(my_function)
のショートカットにすぎない。
こうして my_function には my_decorator の戻り値(my_wrapper)が入り、my_function() と呼び出すことで上記のように出力されます。
デコレータを積み重ねる
def bread(func):
def wrapper():
print "33333"
func()
print "44444"
return wrapper
def ingredients(func):
def wrapper():
print "11111"
func()
print "22222"
return wrapper
def sandwich(food="--ham--"):
print food
sandwich()
# => --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#出力:
# 33333
# 11111
# --ham--
# 22222
# 44444
デコレータ構文を使うと次のようになります。
@bread
@ingredients
def sandwich(food="--ham--"):
print food
sandwich()
#出力:
# 33333
# 11111
# --ham--
# 22222
# 44444
デコレートされた関数に引数を渡す
# ラッパーは単に引数を渡すことができなければならない:
def decorator(func):
def wrapper(arg1, arg2):
print "11111"
func(arg1, arg2)
return wrapper
# デコレータによって返された関数を呼び出している時点で
# 引数を渡すと、ラッパーを呼び出してそれがデコレートされた
# 関数に引数を渡すようになる
@decorator
def hoge_func(hege1, hege2):
print hege1, hege2
hoge_func("aaa", "bbb")
# 出力:
# 11111
# aaa bbb
メソッドをデコレートする
Pythonは、メソッドの第一引数が現在のクラスのインスタンスオブジェクト(self)への参照を持つ点を除けば、メソッドと関数が実際には同じものです。
それはメソッドが(関数と)同じ方法でデコレータを構築できます。
selfを取るという点を忘れずに覚えておきます。
引数には*argsや**kwargsなどのタプルやディクショナリももちろん可能です。
#引数の数値を2倍にするデコレータ
def decorator(func):
def wrapper(self, lie):
lie = lie*2
return func(self, lie)
return wrapper
class hoge(object):
def __init__(self, cnt):
self.cnt = cnt
@decorator
def my_func(self, lie): #インスタンスで指定した数値から引数の数値を引く
print("数値は%dです。" %(self.cnt - lie))
l = hoge(30)
l.my_func(3)
#出力 24
デコレータに引数を付けて呼び出す
デコレータ式を使う場合
def show(text):
def deco(f):
def wp():
print(text)
return(f)
return wp
return deco
@show("spamcall")
def spam():
pass
spam()
#---出力----
spamcall
デコレータ式を使わない場合
def show(text):
def deco(f):
def wp():
print(text)
return(f)
return wp
return deco
def spam():
pass
#関数showの引数に「"spamcall"」が渡され
#関数showの子の関数decoの引数に関数のspamが渡される
spam = show("spamcall")(spam)
spam()
#---出力----
spamcall