func_closureのひみつ
Pythonでデコレータで修飾した関数から、修飾される前の関数を取り出したいというニーズは多いようで
デコレータ式を適用した関数から元の関数名を探す - gumi Engineer’s Blog
デコレータを外す - logiqboard
デコレータを取り除いて見たかった。 - podhmoの日記
のようなブログエントリが上がっているが、Pythonでは元の関数を取り出す確実な手法は用意されていないので諦めていただきたい。デコレータを使って関数を定義しても、Pythonはその情報をどこにも記録していない。
必要であれば http://docs.pylonsproject.org/projects/venusian/dev/ のようにデコレータ自身に細工をする、テスト対象モジュールのインポート時にフックを入れ、修飾せずに元の関数を返すデコレータに置き換えるなどとするしかないだろう。
自由変数・束縛変数・クロージャ
まず、ざっくりと用語の確認をしておこう。
def spam(): ham = 1 def egg(): sausage = 2 print ham, sausage return egg
関数spam()
では、ham
という変数を作成している。このように、代入文などで変数と値を定義している変数のことを、束縛変数と呼ぶ。
関数egg()
でもsausage
という変数を定義しており、これも束縛変数だ。egg()
では他に、自分では定義していない、親関数で定義された変数のham
を参照している。このようにして定義せずに参照されている変数を、自由変数と呼び、自由変数を参照する関数(ここではegg()
)はクロージャ(閉包)を持っている。
自由変数だの閉包だのとめんどくさそうな用語が出てきたが、コンピュータサイエンスっぽくてかっこいいので憶えておくと良いだろう。
以前 http://d.hatena.ne.jp/atsuoishimoto/20101006/1286338675 でクロージャの仕組みを簡単に解説したが、このページの解説はあまり正確ではない。全ての変数はセルオブジェクトに格納されているように書いているが、実際にはセルオブジェクトに格納される変数は自由変数だけで、束縛変数はセルを使用しないのである。この辺の話は、@methaneがPythonのクロージャ - Google スライドで詳しく解説されているので参照していただきたい。
func_closureの中身
関数オブジェクトのfunc_closure/__closure__
には、クロージャが参照している自由変数が全て格納されている。
def spam(): ham=1 # 束縛変数 egg=2 # 束縛変数 def sausage(): return ham+egg # ham, eggは自由変数 return sausage print spam().func_closure
とすると、
(<cell at 0x02CBD8B0: int object at 0x01534704>, <cell at 0x02CBD830: int object at 0x01534710>)
のような結果が得られる。この二つのセルオブジェクトは、sausage()
内の二つの自由変数、ham
とegg
を格納しているセルオブジェクトである。
デコレータはがしにfunc_closureを使えないわけ
これまでの解説で、func_closure
はデコレータはがしには使えない事はご理解いただると思う。単純なデコレータ
def simpledeco(f): # fは束縛変数 def wrapper(): return f() # fは自由変数 return wrapper
では、引数で渡された関数f
はwrapper()
内では自由変数となるため、wrapper()
のfunc_closure
にはf
を参照するセルオブジェクトが登録される。しかし、これはたまたまこのようなコーディングをしたからであって、例えば
def simpledeco(f): # fは束縛変数 def wrapper(f=f): return f() # fは束縛変数 return wrapper
のように引数でf
を渡してしまえばwrapper()
はクロージャを持たなくなり、func_closue
は作成されなくなってしまう。また、
def simpledeco(f): def nuisance(): pass def wrapper(): nuisance() # nuisanceは自由変数 return f() # fは自由変数 return wrapper
とすると、関数nuisance()
もwrapper()
から見れば自由変数になるので、func_closure
にはnuisance
とf
の両方が登録されることになる。後からfunc_closure
を走査しても、どちらがデコレートしている元関数なのか、判断することは難しいだろう。