decorator

ここでは、関数を文字列に置き換えるデコレータをやった。
デコレータがどう処理されるかがわかったので、今度は関数の機能を拡張(?)するデコレータをやってみる。

クロージャを使ったデコレータ(引数なし)

まずは、関数を使ったデコレータから。クロージャの機能を使って実現している。
やっていることは判るんだけど、クロージャだとなんでローカル変数への参照が保持され続けるのかは判っていない。
ここを読んで勉強しよう。

[kobakoba0723@fedora13-intel64 decorator]$ cat deco_closure.py 
#!/usr/bin/env python

def decoF(func):
  print "Now in decoF():", func
  
  def innerF(value):
    import time
    print "Now in innerF():", value
    start = time.time()
    func(value)
    end = time.time()
    print end - start
  
  return innerF

@decoF
def sumUp(value):
  print "Now in sumUp():", value, "   sum up:", sum(range(value))

@decoF
def pow2(value):
  print "Now in pow2():", value, "   pow(2):", pow(value, 2)

print "-------"
sumUp(5)
print "-------"
pow2(5)

[kobakoba0723@fedora13-intel64 decorator]$ python deco_closure.py 
Now in decoF(): <function sumUp at 0x7fdd0d065a28>
Now in decoF(): <function pow2 at 0x7fdd0d06d938>
-------
Now in innerF(): 5
Now in sumUp(): 5    sum up: 10
1.78813934326e-05
-------
Now in innerF(): 5
Now in pow2(): 5    pow(2): 25
9.05990600586e-06
[kobakoba0723@fedora13-intel64 decorator]$ 

インタプリタが@decoFを解釈した時点で、decoF()の解釈が始まる。

[kobakoba0723@fedora13-intel64 decorator]$ python -m pdb deco_closure.py 
> /home/kobakoba0723/decorator/deco_closure.py(3)<module>()
-> def decoF(func):
(Pdb) s
> /home/kobakoba0723/decorator/deco_closure.py(16)<module>()
-> @decoF
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(3)decoF()
-> def decoF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(4)decoF()
-> print "Now in decoF():", func
(Pdb) 
Now in decoF(): <function sumUp at 0x22f2d70>
> /home/kobakoba0723/decorator/deco_closure.py(6)decoF()
-> def innerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(14)decoF()
-> return innerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(14)decoF()-><functio...x22f2c08>
-> return innerF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(20)<module>()
-> @decoF
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(3)decoF()
-> def decoF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(4)decoF()
-> print "Now in decoF():", func
(Pdb) 
Now in decoF(): <function pow2 at 0x22f2320>
> /home/kobakoba0723/decorator/deco_closure.py(6)decoF()
-> def innerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(14)decoF()
-> return innerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(14)decoF()-><functio...x22f2140>
-> return innerF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(24)<module>()
-> print "-------"
(Pdb) 
-------
> /home/kobakoba0723/decorator/deco_closure.py(25)<module>()
-> sumUp(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(6)innerF()
-> def innerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(7)innerF()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(8)innerF()
-> print "Now in innerF():", value
(Pdb) 
Now in innerF(): 5
> /home/kobakoba0723/decorator/deco_closure.py(9)innerF()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(10)innerF()
-> func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(16)sumUp()
-> @decoF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(18)sumUp()
-> print "Now in sumUp():", value, "   sum up:", sum(range(value))
(Pdb) 
Now in sumUp(): 5    sum up: 10
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(18)sumUp()->None
-> print "Now in sumUp():", value, "   sum up:", sum(range(value))
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(11)innerF()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(12)innerF()
-> print end - start
(Pdb) 
3.56497812271
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(12)innerF()->None
-> print end - start
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(26)<module>()
-> print "-------"
(Pdb) 
-------
> /home/kobakoba0723/decorator/deco_closure.py(27)<module>()
-> pow2(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(6)innerF()
-> def innerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(7)innerF()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(8)innerF()
-> print "Now in innerF():", value
(Pdb) 
Now in innerF(): 5
> /home/kobakoba0723/decorator/deco_closure.py(9)innerF()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(10)innerF()
-> func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure.py(20)pow2()
-> @decoF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(22)pow2()
-> print "Now in pow2():", value, "   pow(2):", pow(value, 2)
(Pdb) 
Now in pow2(): 5    pow(2): 25
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(22)pow2()->None
-> print "Now in pow2():", value, "   pow(2):", pow(value, 2)
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(11)innerF()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure.py(12)innerF()
-> print end - start
(Pdb) 
2.64340090752
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(12)innerF()->None
-> print end - start
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure.py(27)<module>()->None
-> pow2(5)
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb) 
The program finished and will be restarted

デバッガの出力を見ていると、sumUpとpow2ではデコレートした結果それぞれの変数に
元々のsumUpとpow2とは異なるアドレス(return innerFの値)が格納されていることがわかる。

関数名 元アドレス 新アドレス
sumUp decoF()->
pow2 decoF()->

ただ、この時点では、innerF()へのアドレスが格納されているだけで、innerF()の中身の解釈は行われない.
それが行われるのは、メイン部分でsumUp(5)/pow2(5)として関数が実行されるとき。
クロージャとしてinnerF()を作っているので、funcにはsumUP/pow2のアドレスが保持された状態でinnerFの解釈が行われる。
sumUp/pow2の中身の解釈が行われるのは、innerF()の中で、func(value)とやっているところ。
ただ、デバッガの出力が"def sumUp(value)"とかでなく"@decoF"となっているのはなんでだろう。
この疑問を横においておくと、めでたくsumUp()/pow2()の解釈が行われ、関数本体のprint文の出力が行われる。

クラスを使ったデコレータ(引数なし)

クラスを使うと、クロージャを使って保持していたローカル変数への参照をインスタンス変数として保持できる。
デコレータはシンタックスシュガーなので、
デコレータとして作成したクラスのインスタンス(デコレートされる関数名を引数として作成)が、
デコレートされる関数のアドレスのかわりに、関数名の変数に代入されれば良い。

class decorator(object):
  ...

@decorator
def func():
  pass
↑と
↑は同じ意味になる
class decorator(object):
  ...

def func():
  pass
func = decorator(func)

関数名の変数にはインスタンスが代入されているため、関数のように呼び出し可能にするためには、
インスタンスを呼び出し可能にしてやる必要がある。

そのため、クラスとしてデコレータを作るためには、以下の2つをクラスに実装する必要がある

  1. デコレートした結果としてインスタンスを返すための__init__(デコレートされる関数を引数に取る必要あり)
  2. インスタンスを呼び出し可能にするための__call__
[kobakoba0723@fedora13-intel64 decorator]$ cat deco_class.py 
#!/usr/bin/env python

class decoClass(object):
  def __init__(self, func):
    print "Now in decoClass():", func
    self.func = func
  def __call__(self, value):
    import time
    print "Now measureTime.__call__()", self.func
    start = time.time()
    self.func(value)
    end = time.time()
    print end - start

@decoClass
def sumUp(value):
  print "Now in sumUp():", value, "    sum up:", sum(range(value))

@decoClass
def pow2(value):
  print "Now in pow2():", value, "    pow(2):", pow(value, 2)

print "-----"
sumUp(5)
print "-----"
pow2(5)

[kobakoba0723@fedora13-intel64 decorator]$ python deco_class.py 
Now in decoClass(): <function sumUp at 0x7fd9046f4a28>
Now in decoClass(): <function pow2 at 0x7fd9046fc9b0>
-----
Now measureTime.__call__() <function sumUp at 0x7fd9046f4a28>
Now in sumUp(): 5     sum up: 10
1.4066696167e-05
-----
Now measureTime.__call__() <function pow2 at 0x7fd9046fc9b0>
Now in pow2(): 5     pow(2): 25
8.10623168945e-06
[kobakoba0723@fedora13-intel64 decorator]$ 

ただ、デバッガの出力を追っかけているとあれ?と思う点がいくつかある。

  1. ステップ実行してみると、classの解釈が3回行われてる(nextで実行すると1回しか行われない)
  2. @decoClassの復帰値として、インスタンスのアドレスが返ってこない
  3. self.func(value)の解釈をするときに、def sumUpではなく@decoClassとなっている(クロージャのときと同じ)
[kobakoba0723@fedora13-intel64 decorator]$ python -m pdb deco_class.py 
> /home/kobakoba0723/decorator/deco_class.py(3)<module>()
-> class decoClass(object):
(Pdb) s
--Call--
> /home/kobakoba0723/decorator/deco_class.py(3)decoClass()
-> class decoClass(object):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(3)decoClass()
-> class decoClass(object):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(4)decoClass()
-> def __init__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(7)decoClass()
-> def __call__(self, value):
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class.py(7)decoClass()->{'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {'__call__': <functio...x1bc9320>, '__init__': <functio...x1bc9c08>, '__module__': '__main__', '__return__': {...}}}}}}}
-> def __call__(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(15)<module>()
-> @decoClass
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(4)__init__()
-> def __init__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(5)__init__()
-> print "Now in decoClass():", func
(Pdb) 
Now in decoClass(): <function sumUp at 0x1bc9d70>
> /home/kobakoba0723/decorator/deco_class.py(6)__init__()
-> self.func = func
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class.py(6)__init__()->None
-> self.func = func
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(19)<module>()
-> @decoClass
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(4)__init__()
-> def __init__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(5)__init__()
-> print "Now in decoClass():", func
(Pdb) 
Now in decoClass(): <function pow2 at 0x1bc9140>
> /home/kobakoba0723/decorator/deco_class.py(6)__init__()
-> self.func = func
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class.py(6)__init__()->None
-> self.func = func
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(23)<module>()
-> print "-----"
(Pdb) 
-----
> /home/kobakoba0723/decorator/deco_class.py(24)<module>()
-> sumUp(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(7)__call__()
-> def __call__(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(8)__call__()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(9)__call__()
-> print "Now measureTime.__call__()", self.func
(Pdb) 
Now measureTime.__call__() <function sumUp at 0x1bc9d70>
> /home/kobakoba0723/decorator/deco_class.py(10)__call__()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(11)__call__()
-> self.func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(15)sumUp()
-> @decoClass
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(17)sumUp()
-> print "Now in sumUp():", value, "    sum up:", sum(range(value))
(Pdb) 
Now in sumUp(): 5     sum up: 10
--Return--
> /home/kobakoba0723/decorator/deco_class.py(17)sumUp()->None
-> print "Now in sumUp():", value, "    sum up:", sum(range(value))
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(12)__call__()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(13)__call__()
-> print end - start
(Pdb) 
2.11219906807
--Return--
> /home/kobakoba0723/decorator/deco_class.py(13)__call__()->None
-> print end - start
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(25)<module>()
-> print "-----"
(Pdb) 
-----
> /home/kobakoba0723/decorator/deco_class.py(26)<module>()
-> pow2(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(7)__call__()
-> def __call__(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(8)__call__()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(9)__call__()
-> print "Now measureTime.__call__()", self.func
(Pdb) 
Now measureTime.__call__() <function pow2 at 0x1bc9140>
> /home/kobakoba0723/decorator/deco_class.py(10)__call__()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(11)__call__()
-> self.func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class.py(19)pow2()
-> @decoClass
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(21)pow2()
-> print "Now in pow2():", value, "    pow(2):", pow(value, 2)
(Pdb) 
Now in pow2(): 5     pow(2): 25
--Return--
> /home/kobakoba0723/decorator/deco_class.py(21)pow2()->None
-> print "Now in pow2():", value, "    pow(2):", pow(value, 2)
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(12)__call__()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class.py(13)__call__()
-> print end - start
(Pdb) 
7.56025600433
--Return--
> /home/kobakoba0723/decorator/deco_class.py(13)__call__()->None
-> print end - start
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class.py(26)<module>()->None
-> pow2(5)
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb) 
The program finished and will be restarted

1.と2.については、デコレータとして作ったクラスじゃなくてもこういう動きをするみたい。
試しに __init__ の中でreturn ってやってみたら、TypeErrorって怒られたし。
とりあえずは、そういうもんなんだと思うことにした。

[kobakoba0723@fedora13-intel64 decorator]$ cat work.py
#!/usr/bin/env python

class C1(object):
  def __init__(self, value):
    self.value = value
    return "hogehoge"

sample = C1(1)
print type(sample)

[kobakoba0723@fedora13-intel64 decorator]$ python -m pdb work.py
> /home/kobakoba0723/decorator/work.py(3)<module>()
-> class C1(object):
(Pdb) s
--Call--
> /home/kobakoba0723/decorator/work.py(3)C1()
-> class C1(object):
(Pdb) 
> /home/kobakoba0723/decorator/work.py(3)C1()
-> class C1(object):
(Pdb) 
> /home/kobakoba0723/decorator/work.py(4)C1()
-> def __init__(self, value):
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/work.py(4)C1()->{'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {'__init__': <functio...x180ded8>, '__module__': '__main__', '__return__': {...}}}}}}}
-> def __init__(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/work.py(8)<module>()
-> sample = C1(1)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/work.py(4)__init__()
-> def __init__(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/work.py(5)__init__()
-> self.value = value
(Pdb) 
> /home/kobakoba0723/decorator/work.py(6)__init__()
-> return "hogehoge"
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/work.py(6)__init__()->'hogehoge'
-> return "hogehoge"
(Pdb) 
TypeError: "__init__() should return None, not 'str'"
> /home/kobakoba0723/decorator/work.py(8)<module>()
-> sample = C1(1)
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/work.py(8)<module>()->None
-> sample = C1(1)
(Pdb) 
TypeError: "__init__() should return None, not 'str'"
> <string>(1)<module>()->None
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb) 
Traceback (most recent call last):
  File "/usr/lib64/python2.6/pdb.py", line 1283, in main
    pdb._runscript(mainpyfile)
  File "/usr/lib64/python2.6/pdb.py", line 1202, in _runscript
    self.run(statement)
  File "/usr/lib64/python2.6/bdb.py", line 368, in run
    exec cmd in globals, locals
  File "<string>", line 1, in <module>
  File "work.py", line 8, in <module>
    sample = C1(1)
TypeError: __init__() should return None, not 'str'
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program

クロージャを使ったデコレータ(引数あり)

引数の値を保持するために、クロージャクロージャ(?)を使ってデコレータを実現する。
関数が3つ続く形になってしまうので、分けわからんくなってしまいそう。
引数が無かった時のクロージャに対して、引数を保存するためのクロージャを外側にかぶせる。

[kobakoba0723@fedora13-intel64 decorator]$ cat deco_closure_arg.py 
#!/usr/bin/env python

def decoF(comment):
  print "Now in decoF():", comment
  
  def innerF(func):
    def innerInnerF(value):
      import time
      print "Now innerF():"+comment+":", func
      start = time.time()
      func(value)
      end = time.time()
      print end - start
    
    return innerInnerF
  
  return innerF

@decoF("sumUp function")
def sumUp(value):
  print "Now in sumUp():", value, "   sum up:", sum(range(value))

@decoF("pow2 function")
def pow2(value):
  print "Now in pow2():", value, "   pow(2):", pow(value, 2)

print "-------"
sumUp(5)
print "-------"
pow2(5)

[kobakoba0723@fedora13-intel64 decorator]$ python deco_closure_arg.py 
Now in decoF(): sumUp function
Now in decoF(): pow2 function
-------
Now innerF():sumUp function: <function sumUp at 0x7f280b377938>
Now in sumUp(): 5    sum up: 10
1.8835067749e-05
-------
Now innerF():pow2 function: <function pow2 at 0x7f280b377a28>
Now in pow2(): 5    pow(2): 25
9.77516174316e-06
[kobakoba0723@fedora13-intel64 decorator]$ 

@decoFが解釈されるタイミングでは、

  • def innerF が実行され、innerF という変数に関数のアドレスが格納される
  • 格納されたアドレスが返される(return innerF)

っていう処理がされているんだろうなぁと想像してたけど、
デバッガの出力を見てみると、さらに2つのことが処理されている。

  • def innerF が実行され、innerF という変数に関数のアドレスが格納される
  • 格納されたアドレスが返される(return innerF)
  • def innerF の解釈が始まり、def innerInnerF が実行され、innerInnerF という変数に関数のアドレスが格納される
  • 格納されたアドレスが返される(return innerInnerF)
[kobakoba0723@fedora13-intel64 decorator]$ python -m pdb deco_closure_arg.py 
> /home/kobakoba0723/decorator/deco_closure_arg.py(3)<module>()
-> def decoF(comment):
(Pdb) s
> /home/kobakoba0723/decorator/deco_closure_arg.py(19)<module>()
-> @decoF("sumUp function")
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(3)decoF()
-> def decoF(comment):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(4)decoF()
-> print "Now in decoF():", comment
(Pdb) 
Now in decoF(): sumUp function
> /home/kobakoba0723/decorator/deco_closure_arg.py(6)decoF()
-> def innerF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(17)decoF()
-> return innerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(17)decoF()-><functio...x1e55320>
-> return innerF
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(6)innerF()
-> def innerF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(7)innerF()
-> def innerInnerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(15)innerF()
-> return innerInnerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(15)innerF()-><functio...x1db9050>
-> return innerInnerF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(23)<module>()
-> @decoF("pow2 function")
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(3)decoF()
-> def decoF(comment):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(4)decoF()
-> print "Now in decoF():", comment
(Pdb) 
Now in decoF(): pow2 function
> /home/kobakoba0723/decorator/deco_closure_arg.py(6)decoF()
-> def innerF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(17)decoF()
-> return innerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(17)decoF()-><functio...x1e55320>
-> return innerF
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(6)innerF()
-> def innerF(func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(7)innerF()
-> def innerInnerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(15)innerF()
-> return innerInnerF
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(15)innerF()-><functio...x1dc8848>
-> return innerInnerF
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(27)<module>()
-> print "-------"
(Pdb) 
-------
> /home/kobakoba0723/decorator/deco_closure_arg.py(28)<module>()
-> sumUp(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(7)innerInnerF()
-> def innerInnerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(8)innerInnerF()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(9)innerInnerF()
-> print "Now innerF():"+comment+":", func
(Pdb) 
Now innerF():sumUp function: <function sumUp at 0x1e55140>
> /home/kobakoba0723/decorator/deco_closure_arg.py(10)innerInnerF()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(11)innerInnerF()
-> func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(19)sumUp()
-> @decoF("sumUp function")
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(21)sumUp()
-> print "Now in sumUp():", value, "   sum up:", sum(range(value))
(Pdb) 
Now in sumUp(): 5    sum up: 10
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(21)sumUp()->None
-> print "Now in sumUp():", value, "   sum up:", sum(range(value))
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(12)innerInnerF()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(13)innerInnerF()
-> print end - start
(Pdb) 
2.66387104988
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(13)innerInnerF()->None
-> print end - start
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(29)<module>()
-> print "-------"
(Pdb) 
-------
> /home/kobakoba0723/decorator/deco_closure_arg.py(30)<module>()
-> pow2(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(7)innerInnerF()
-> def innerInnerF(value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(8)innerInnerF()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(9)innerInnerF()
-> print "Now innerF():"+comment+":", func
(Pdb) 
Now innerF():pow2 function: <function pow2 at 0x1dc8d70>
> /home/kobakoba0723/decorator/deco_closure_arg.py(10)innerInnerF()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(11)innerInnerF()
-> func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_closure_arg.py(23)pow2()
-> @decoF("pow2 function")
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(25)pow2()
-> print "Now in pow2():", value, "   pow(2):", pow(value, 2)
(Pdb) 
Now in pow2(): 5    pow(2): 25
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(25)pow2()->None
-> print "Now in pow2():", value, "   pow(2):", pow(value, 2)
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(12)innerInnerF()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_closure_arg.py(13)innerInnerF()
-> print end - start
(Pdb) 
3.99925494194
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(13)innerInnerF()->None
-> print end - start
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_closure_arg.py(30)<module>()->None
-> pow2(5)
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb) 
The program finished and will be restarted

ただ、よくよく出力を見ていると復帰値のアドレス値が想像していた値と違う。
return innerInnerF の値と print "Now innerF():"+comment+":", func における func の値が違う。

関数名 想定したアドレス 実際のアドレス
sumUp innerF()->
pow2 innerF()->

実行結果は想定していた通りだけど、アドレスが違うってことは、たまたまうまく動いているってことかも。
わかったつもりになっている状態に陥ってるかもしれんなぁ。。。

あと、もう一つ興味深いのは、return innerFのアドレスが、sumUpとpow2で同じってこと。
がわのクロージャ(def innerF)の領域は1つだけ確保されて、
2つ(sumUp の 'return innerF' と pow2 の 'return innerF')から参照されているってことなのか!?

関数名 復帰アドレス
sumUp decoF()->
pow2 decoF()->

クラスを使ったデコレータ(引数あり)

クラスの場合は、引数を新しいインスタンス変数として格納することで、値を保持することが出来る。
クロージャ同様に、もう一段下のメソッドを用意して、その中で元々__call__でやっていた処理を実装する。

[kobakoba0723@fedora13-intel64 decorator]$ cat deco_class_arg.py 
#!/usr/bin/env python

class decoClass(object):
  def __init__(self, comment):
    print "Now in decoClass():", comment
    self.comment = comment
  def __call__(self, func):
    print "Now measureTime.__cal__()", func
    self.func = func
    return self.__decorate
  def __decorate(self, value):
    import time
    print "Now __decorate():"+self.comment+": ", value
    start = time.time()
    self.func(value)
    end = time.time()
    print end - start

@decoClass("sumUp function")
def sumUp(value):
  print "Now in sumUp():", value, "    sum up:", sum(range(value))

@decoClass("pow2 function")
def pow2(value):
  print "Now in pow2():", value, "    pow(2):", pow(value, 2)

print "-----"
sumUp(5)
print "-----"
pow2(5)

[kobakoba0723@fedora13-intel64 decorator]$ python deco_class_arg.py 
Now in decoClass(): sumUp function
Now measureTime.__cal__() <function sumUp at 0x7f5d0936aa28>
Now in decoClass(): pow2 function
Now measureTime.__cal__() <function pow2 at 0x7f5d09372a28>
-----
Now __decorate():sumUp function:  5
Now in sumUp(): 5     sum up: 10
1.28746032715e-05
-----
Now __decorate():pow2 function:  5
Now in pow2(): 5     pow(2): 25
8.82148742676e-06
[kobakoba0723@fedora13-intel64 decorator]$ 

デコレータの解釈では、クラスにおいても クロージャのとき同様に思っていたよりも処理が進んでいる。
@自分の想定

@実際の処理

  • __init__が実行され、decoClassのインスタンスが生成される
  • __call__が実行される

少し後で考えてみたら、関数デコレータはシンタックスシュガーなんだから、

@decoClass("sumUp function")
def sumUp(value):
  ...
↑は
↑と同じ
def sumUp(value):
  ...
sumUp = decoClass("sumUp function")(sumUp)

decoClass("sumUp function")は__init__で処理され、インスタンス(X)が返される。
けど、x(sumUp)は、__call__で処理されるから、__call__まで実行されて当然なわけだ。
クロージャの時も同じ理由から、def innerInnerFまで実行されるのか。

[kobakoba0723@fedora13-intel64 decorator]$ python -m pdb deco_class_arg.py 
> /home/kobakoba0723/decorator/deco_class_arg.py(3)<module>()
-> class decoClass(object):
(Pdb) s
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(3)decoClass()
-> class decoClass(object):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(3)decoClass()
-> class decoClass(object):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(4)decoClass()
-> def __init__(self, comment):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(7)decoClass()
-> def __call__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(11)decoClass()
-> def __decorate(self, value):
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(11)decoClass()->{'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {'__call__': <functio...x13fe320>, '__init__': <functio...x13fec08>, '__module__': '__main__', '__return__': {...}, ...}, ...}, ...}, ...}, ...}, ...}
-> def __decorate(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(19)<module>()
-> @decoClass("sumUp function")
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(4)__init__()
-> def __init__(self, comment):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(5)__init__()
-> print "Now in decoClass():", comment
(Pdb) 
Now in decoClass(): sumUp function
> /home/kobakoba0723/decorator/deco_class_arg.py(6)__init__()
-> self.comment = comment
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(6)__init__()->None
-> self.comment = comment
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(7)__call__()
-> def __call__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(8)__call__()
-> print "Now measureTime.__cal__()", func
(Pdb) 
Now measureTime.__cal__() <function sumUp at 0x13fed70>
> /home/kobakoba0723/decorator/deco_class_arg.py(9)__call__()
-> self.func = func
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(10)__call__()
-> return self.__decorate
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(10)__call__()-><bound m...1400150>>
-> return self.__decorate
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(23)<module>()
-> @decoClass("pow2 function")
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(4)__init__()
-> def __init__(self, comment):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(5)__init__()
-> print "Now in decoClass():", comment
(Pdb) 
Now in decoClass(): pow2 function
> /home/kobakoba0723/decorator/deco_class_arg.py(6)__init__()
-> self.comment = comment
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(6)__init__()->None
-> self.comment = comment
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(7)__call__()
-> def __call__(self, func):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(8)__call__()
-> print "Now measureTime.__cal__()", func
(Pdb) 
Now measureTime.__cal__() <function pow2 at 0x1362050>
> /home/kobakoba0723/decorator/deco_class_arg.py(9)__call__()
-> self.func = func
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(10)__call__()
-> return self.__decorate
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(10)__call__()-><bound m...14001d0>>
-> return self.__decorate
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(27)<module>()
-> print "-----"
(Pdb) 
-----
> /home/kobakoba0723/decorator/deco_class_arg.py(28)<module>()
-> sumUp(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(11)__decorate()
-> def __decorate(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(12)__decorate()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(13)__decorate()
-> print "Now __decorate():"+self.comment+": ", value
(Pdb) 
Now __decorate():sumUp function:  5
> /home/kobakoba0723/decorator/deco_class_arg.py(14)__decorate()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(15)__decorate()
-> self.func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(19)sumUp()
-> @decoClass("sumUp function")
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(21)sumUp()
-> print "Now in sumUp():", value, "    sum up:", sum(range(value))
(Pdb) 
Now in sumUp(): 5     sum up: 10
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(21)sumUp()->None
-> print "Now in sumUp():", value, "    sum up:", sum(range(value))
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(16)__decorate()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(17)__decorate()
-> print end - start
(Pdb) 
2.4398560524
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(17)__decorate()->None
-> print end - start
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(29)<module>()
-> print "-----"
(Pdb) 
-----
> /home/kobakoba0723/decorator/deco_class_arg.py(30)<module>()
-> pow2(5)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(11)__decorate()
-> def __decorate(self, value):
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(12)__decorate()
-> import time
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(13)__decorate()
-> print "Now __decorate():"+self.comment+": ", value
(Pdb) 
Now __decorate():pow2 function:  5
> /home/kobakoba0723/decorator/deco_class_arg.py(14)__decorate()
-> start = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(15)__decorate()
-> self.func(value)
(Pdb) 
--Call--
> /home/kobakoba0723/decorator/deco_class_arg.py(23)pow2()
-> @decoClass("pow2 function")
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(25)pow2()
-> print "Now in pow2():", value, "    pow(2):", pow(value, 2)
(Pdb) 
Now in pow2(): 5     pow(2): 25
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(25)pow2()->None
-> print "Now in pow2():", value, "    pow(2):", pow(value, 2)
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(16)__decorate()
-> end = time.time()
(Pdb) 
> /home/kobakoba0723/decorator/deco_class_arg.py(17)__decorate()
-> print end - start
(Pdb) 
3.17621397972
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(17)__decorate()->None
-> print end - start
(Pdb) 
--Return--
> /home/kobakoba0723/decorator/deco_class_arg.py(30)<module>()->None
-> pow2(5)
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb) 
The program finished and will be restarted

こうやってデバッガの出力を眺めていると、中でどんなことが行われているか目に見えてわかる。
デバッガって、バグを潰す時だけじゃなくて*1
プログラムの動きを理解する時にもスゴい役に立つことが身を以てわかった気がする。
おかげで、曖昧な理解な部分も浮き彫りになっちゃったけど*2

デコレータを使うって参照先を置き換えると、元関数のname/dict等々も置き換えられてしまう

なので、デコレータの中で、元関数の変数の値を引き継いでやらないといけない。
functoolsモジュールを使うと、wraps()を関数デコレータとして呼び出すだけで出来てしまう。

デコレータのサンプル

ここにたくさん載ってるから、
時間見つけて一度読んでみよう。

*1:こっちの用途もまともに使ったことがないけど

*2:これはこれでとってもありがたいことだね