デコレータを取り除いて見たかった。

最終的には、汎用的なデコレータ除去の方法が見つからなかった。

対象

  • pythonのデコレータが分かる
  • クロージャが分かる。
  • デコレータをかけた関数のデコレータを取り除きたいと思っている

色々調べて見たところ、デコレータを付与する前の関数を取り出す方法が分かった。。。と、思った。

# -*- 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)

で良い。

まとめ

  • デコレータというのはf = wrap(f)のsyntax-sugarでしかない。
  • デコレータの定義の方法に2種類の方法がある。
    • 関数でデコレータを定義(クロージャの機能を利用)
    • クラスデコレータを定義(__call__の機能(callable object)を利用)
  • __closure__で、クロージャの保持する環境にアクセスできる。

デコレータを取り除いたりするには

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