デコレータをイラストで理解する

python のデコレータというのを理解すべく, 高階関数の振る舞いを図に描いてみました(・ん・)ノ

(define (foo)
  (print "hello:)"))

ではこの定義した手続きの foo と (foo) の振る舞いを見てみます.

ネコさん: 環境
小人さん: 評価器
ハコ: 変数を束縛している値の格納場所
../../../_images/01.png

foo を呼び出したとき(左), 小人さんは foo という変数に束縛された関数オブジェクトを呼び出します. 一方, (foo) を呼び出したとき(右), 小人さんは foo という変数に束縛された手続きを評価した結果の値を返します. 小人さんは手続きを評価するときは, その関数オブジェクトを別の環境にコピってきてその評価の結果を出力し, それが終わったらコピってきた環境は破棄します. foo 呼び出しと, (foo) 呼び出し, 両者は全く別物ですね!

ではでは, この理解の上にデコレータ(というか高階関数)の振る舞いを考えてみます.

gosh> (define (deco func)
        (define (wrapped)
          (func))
        wrapped)
deco
gosh> (define (foo)
        (print "hello"))
foo
gosh> deco
#<closure deco>
gosh> foo
#<closure foo>
gosh> (set! foo (deco foo))
#<closure (deco wrapped)>
gosh> foo
#<closure (deco wrapped)>
gosh> (foo)
hello
#<undef>

(set! foo (deco foo)) というのは python では

foo = deco(foo)

つまり,

@deco
def foo():
    # code

のことです.

では小人さんが (set! foo (deco foo)) をどのように評価するか見ていきます.

set! は特殊形式なので, (1) まず代入される値を評価し, その次に (2) 変数がその値に束縛されます.

../../../_images/02.png

最後に, デコレータを作用させた foo の foo 呼び出しと (foo) 呼び出しを見てみます.

../../../_images/03.png

先ほどの図と同じように理解できますね. foo を呼び出したとき(左), 小人さんは foo という変数に束縛された関数オブジェクトを呼び出します. そして (foo) を呼び出したとき(右), 小人さんは foo という変数に束縛された手続きを評価した結果の値を返します. この結果の値というのが, wrapped 手続きを評価した結果の値に相当します. 高階関数は関数オブジェクトを引数にとる関数という理解でいいのでしょうか.

絵を描きながらの理解でした!

はっ ...気づいたら Scheme を書いてましたが気のせいです. このブログはぱいそんにっきですから!

ちなみに, python ではこうなりますね!

>>> def deco(func):
...     def wrapped():
...             func()
...     return wrapped
...
>>> def foo():
...     print 'hello'
...
>>> foo = deco(foo)
>>> foo
<function wrapped at 0x1057107d0>
>>> deco
<function deco at 0x105710050>
>>> foo.__name__
'wrapped'
>>> foo()
hello
>>> @deco
... def bar():
...     print 'hello'
...
>>> bar
<function wrapped at 0x105715578>
>>> bar.__name__
'wrapped'
>>> bar()
hello

これでもうデコレータこわくありません(^^)