デコレータを取り除いて見たかった。
最終的には、汎用的なデコレータ除去の方法が見つからなかった。
対象
色々調べて見たところ、デコレータを付与する前の関数を取り出す方法が分かった。。。と、思った。
# -*- coding:utf-8 -*- ### decoraltors def inc(f): """+1するデコレータ""" def _inc(x): return f(x+1) return _inc def dec(f): """-1するデコレータ""" def _dec(x): return f(x+1) return _dec import unittest from random import random #### def identity(x): """identity""" return x @inc def add1(x): """\x -> x + 1""" return x @inc @inc def add2(x): """\x -> x + 1 + 1 -> x + 2""" return x @inc @dec @inc def add1_(x): """\x -> x + 1 - 1 + 1 = x + 1""" return x class ExtractOriginalFunction(unittest.TestCase): def test_add1_is_plus1_collectly(self): """ @incで+1した値は実際に+1した値と等しいか? (incデコレータは適切に定義できているか?) """ n = random() self.assertEqual(identity(n)+1, add1(n)) def test_add1_has_closure(self): """ デコレータを付加された関数はfunc_closureを持っている。 func_closureは\"cell\"オブジェクトのリスト """ for closure in add1.func_closure: self.assertEqual(closure.__class__.__name__, "cell") def test_extract_add1_original(self): """ add1のデコレータを覗いたものは、identityを出力が等しいか? (func_closureの先頭要素のcellオブジェクトは、@incを付加する前の関数と同じか?) """ n = random() add1_original = add1.func_closure[0].cell_contents self.assertEqual(add1_original(n), identity(n)) ## デコレータを取り除くにはこうすれば良さそう。 def naked_function(f): """関数に付与されているデコレータを取り除いた関数を返す。""" while getattr(f, "__closure__"): f = f.__closure__[0].cell_contents return f class NakedFunctionTestCase(unittest.TestCase): def test_extract_add1_original_with_naked_function(self): """関数に付与された1つのデコレータが取り除けているか? """ n = random() naked_fn = naked_function(add1) self.assertEqual(naked_fn(n), identity(n)) def test_extract_add2_original_with_naked_function(self): """関数に付与された2つのデコレータが取り除けているか? """ n = random() naked_fn = naked_function(add2) self.assertEqual(naked_fn(n), identity(n)) def test_extract_add1__original_with_naked_function(self): """関数に付与された2種類のデコレータ3つが取り除けているか? """ n = random() naked_fn = naked_function(add1_) self.assertEqual(naked_fn(n), identity(n)) unittest.main()
テストは通る。これで良いのかな?
...... ---------------------------------------------------------------------- Ran 6 tests in 0.000s OK
と思いきや、関数にデコレータを付与する方法はもう一つあった。
- デコレータ用の関数を定義
- デコレータ用のクラスを定義
クラスの方をためしてなかった。
class AnnoyingDecorator(object): def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): print "bbbbbbbbbbbbbbbbbbbbbb!" self.f(*args, **kwargs) @AnnoyingDecorator def identity2(x): return x print naked_function(identity2) ## AttributeError: 'AnnoyingDecorator' object has no attribute '__closure__'
まー、良く考えたら当然で、デコレータを取り除く方法として上の方法がある訳じゃない。
名前の通り、func_closure/__closure__で取れるのは、オブジェクト(関数)の持っているクロージャであって、
実際のところデコレータとは何の関連もない。単に関数デコレータというのは、
f = wrap(f)
のsytax-sugarでしかない。__closure__が存在する云々という話は、そもそもデコレータとは関係ない。
単に、関数でデコレータを定義する際にクロージャの機能を使っているに過ぎない。
同様に、classの機能を使ってデコレータを作成しているのなら、元の関数云々というのは単にコンストラクタの中で、
束縛されたオブジェクトの状態に過ぎない(上の例では、__init__で束縛されているself.)
だから、元の関数が欲しければ
print identity2.f(10)
で良い。
まとめ
デコレータを取り除いたりするには
venusianを使うと良いらしい。
追記
トラックバックを見て
cellオブジェクトに入るのは、内部の関数の中で束縛されていない変数(外の環境に値を探しに行く必要がある変数=自由変数)
なので、__closure__[0]に入るのがデコレータを適用する前の関数とは限らない。
def f(): x = 10 y = 20 z = 30 def g(z=z): # zは束縛変数 return x + y + z # x, yは自由変数 return g ## 自由変数は__closure__に入って、束縛変数は__closure__に入らない。 print f().__closure__ # (<cell at 0x1618478: int object at 0x15a6170>, <cell at 0x16184b0: int object at 0x15a6260>) print f().__closure__[0].cell_contents # => 20 print f().__closure__[1].cell_contents # => 10