2015-10-21 22 views
4

所有裝飾的基礎應用類的裝飾,我使用所有的裝飾方法如何在對方法

import inspect 

def decallmethods(decorator, prefix='test_'): 
    def dectheclass(cls): 
    for name, m in inspect.getmembers(cls, inspect.ismethod): 
     if name.startswith(prefix): 
     setattr(cls, name, decorator(m)) 
    return cls 
    return dectheclass 


@decallmethods(login_testuser) 
class TestCase(object): 
    def setUp(self): 
     pass 

    def test_1(self): 
     print "test_1()" 

    def test_2(self): 
     print "test_2()" 

這是工作,但它適用在上面,如果我有其他裝飾的這種方式。

我的意思是

現在的結果是

@login_testuser 
@other 
def test_2(self): 
    print "test_2()" 

但我想

@other 
@login_testuser 
def test_2(self): 
    print "test_2()" 
+0

您可能想考慮如何將代碼從'test_2 = login_testuser(other(test_2))'轉換爲'test_2 = other(login_testuser(test_2))'。我的建議是,你想到另一種避免你測試中的'other'裝飾器的方法。雖然有許多方法可以操縱裝飾器形成的閉合,但它會非常混亂。 – metatoaster

+0

@metatoaster。你能否給我一些想法,我該如何做到這一點在關閉。我有很多方法,所以不可能重複。 –

+0

我建議您編輯標題以反映您正在做什麼的真實意圖,例如「將裝飾器注入其已裝飾的原始功能」。或者這個效果的東西。結果比我想象的要難得多。 – metatoaster

回答

8

這肯定是一個的想法,但你想做的事可以做在一定程度上,這需要花費很多時間來解釋。首先,不要將裝飾器看作語法糖,而是將它們視爲它們的本質:一個函數(即閉包),其中存在一個函數。現在,這是出路,假設我們有一個函數:

def operation(a, b): 
    print('doing operation') 
    return a + b 

只要它會做到這一點

>>> hi = operation('hello', 'world') 
doing operation 
>>> print(hi) 
helloworld 

現在定義之前打印的東西裝飾,並調用其內部函數後(相當於你想以後裝飾器other裝飾):

def other(f): 
    def other_inner(*a, **kw): 
     print('other start') 
     result = f(*a, **kw) 
     print('other finish') 
     return result 
    return other_inner 

就這樣,建立一個新的功能和裝飾

@other 
def o_operation(a, b): 
    print('doing operation') 
    return a + b 

記憶,這基本上等同於o_operation = other(operation)

運行是爲了確保它的工作原理:

>>> r2 = o_operation('some', 'inner') 
other start 
doing operation 
other finish 
>>> print(r2) 
someinner 

最後,你要調用之前operation但不d_operation最後的裝飾,但與您現有的代碼在此產生效果:

def inject(f): 
    def injected(*a, **kw): 
     print('inject start') 
     result = f(*a, **kw) 
     print('inject finish') 
     return result 
    return injected 

@inject 
@other 
def i_o_operation(a, b): 
    print('doing operation') 
    return a + b 

運行上面:

>>> i_o_operation('hello', 'foo') 
inject start 
other start 
doing operation 
other finish 
inject finish 
'hellofoo' 

如前所述裝飾真的倒閉,因此,這就是爲什麼它可能有一個項目內是有效的實例化內。您可以通過__closure__屬性要達到這些目標:

>>> i_o_operation.__closure__ 
(<cell at 0x7fc0eabd1fd8: function object at 0x7fc0eabce7d0>,) 
>>> i_o_operation.__closure__[0].cell_contents 
<function other_inner at 0x7fc0eabce7d0> 
>>> print(i_o_operation.__closure__[0].cell_contents('a', 'b')) 
other start 
doing operation 
other finish 
ab 

看到這是如何有效地調用直接injected瓶蓋內的功能,如果得到解開。如果可以用進行注射的那個來代替這種封閉,該怎麼辦?對於我們所有的保護,__closure__cell.cell_contents是隻讀的。需要做的是通過使用FunctionType功能構造函數(在types模塊中找到)構建全新的功能,並使用預期的閉包功能

回到問題。因爲我們現在擁有的是:

i_o_operation = inject(other(operation)) 

而我們要的是

o_i_operation = other(inject(operation)) 

我們有效地必須以某種方式從i_o_operation剝離調用other,不知何故與inject繞到它製作o_i_operation。 (龍突破後如下圖)


首先,構造採取封蓋級深(使f將只包含原operation調用),有效地調用inject(operation)功能,但它與生成的代碼混合inject(f)

i_operation = FunctionType(
    i_o_operation.__code__, 
    globals=globals(), 
    closure=i_o_operation.__closure__[0].cell_contents.__closure__, 
) 

由於i_o_operationinject(f)的結果,我們可以採取的代碼產生一個新的功能。 globals是必需的形式,最後是關閉嵌套關卡,並生成函數的第一部分。確認沒有調用other

>>> i_operation('test', 'strip') 
inject start 
doing operation 
inject finish 
'teststrip' 

整潔。但是我們仍然希望將other封裝在此之外,以最終生成o_i_operation。我們需要以某種方式把我們在一個封閉生產這種新的功能和方式做到這一點是創建產生一個

def closure(f): 
    def surrogate(*a, **kw): 
     return f(*a, **kw) 
    return surrogate 

,簡單地用它來構建並提取我們的封閉的替代功能

o_i_operation = FunctionType(
    i_o_operation.__closure__[0].cell_contents.__code__, 
    globals=globals(), 
    closure=closure(i_operation).__closure__, 
) 

調用此:

>>> o_i_operation('job', 'complete') 
other start 
inject start 
doing operation 
inject finish 
other finish 
'jobcomplete' 

看起來我們終於得到了我們所需要的。雖然這並不能完全回答你確切的問題,但是這開始走向正確的軌道,但已經非常多毛。


現在的實際問題:這將確保一個裝飾功能最內(最終)給定的前原可調用的,未經修飾的功能的功能 - 即對於一個給定的targetf(g(...(callable)),我們想模仿結果給出f(g(...(target(callable))))。這是代碼:

from types import FunctionType 

def strip_decorators(f): 
    """ 
    Strip all decorators from f. Assumes each are functions with a 
    closure with a first cell being the target function. 
    """ 

    # list of not the actual decorator, but the returned functions 
    decorators = [] 
    while f.__closure__: 
     # Assume first item is the target method 
     decorators.append(f) 
     f = f.__closure__[0].cell_contents 
    return decorators, f 

def inject_decorator(decorator, f): 
    """ 
    Inject a decorator to the most inner function within the stack of 
    closures in `f`. 
    """ 

    def closure(f): 
     def surrogate(*a, **kw): 
      return f(*a, **kw) 
     return surrogate 

    decorators, target_f = strip_decorators(f) 
    result = decorator(target_f) 

    while decorators: 
     # pop out the last one in 
     decorator = decorators.pop() 
     result = FunctionType(
      decorator.__code__, 
      globals=globals(), 
      closure=closure(result).__closure__, 
     ) 

    return result 

爲了測試這個,我們使用了一個典型的use-case-html標籤示例。

def italics(f): 
    def i(s): 
     return '<i>' + f(s) + '</i>' 
    return i 

def bold(f): 
    def b(s): 
     return '<b>' + f(s) + '</b>' 
    return b 

def underline(f): 
    def u(s): 
     return '<u>' + f(s) + '</u>' 
    return u 

@italics 
@bold 
def hi(s): 
    return s 

運行測試。

>>> hi('hello') 
'<i><b>hello</b></i>' 

我們的目標是注入underline裝飾(特別是u(hi)可調用)到最內罩。這是可以做到像這樣,在功能上面我們已經定義:

>>> hi_u = inject_decorator(underline, hi) 
>>> hi_u('hello') 
'<i><b><u>hello</u></b></i>' 

與未修飾功能的工作原理:

>>> def pp(s): 
...  return s 
... 
>>> pp_b = inject_decorator(bold, pp) 
>>> pp_b('hello') 
'<b>hello</b>' 

一個主要假設是在重寫的這第一個刪節版製作,這是鏈中的所有裝飾只有一個封閉長度,一個元素是被裝飾的功能。以這個裝飾,例如:

def prefix(p): 
    def decorator(f): 
     def inner(*args, **kwargs): 
      new_args = [p + a for a in args] 
      return f(*new_args, **kwargs) 
     return inner 
    return decorator 

用法示例:

>>> @prefix('++') 
... def prefix_hi(s): 
...  return s 
... 
>>> prefix_hi('test') 
'++test' 

現在嘗試注入bold裝飾,像這樣:

>>> prefix_hi_bold = inject_decorator(bold, prefix_hi) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 18, in inject_decorator 
ValueError: inner requires closure of length 2, not 1 

這僅僅是因爲封閉的decorator內形成prefix有兩個元素,一個是前綴字符串p,第二個是實際功能,inner嵌套在內部,期望這兩個在其關閉。解決這個問題需要更多的代碼來分析和重構細節。


無論如何,這解釋了相當多的時間和文字,所以我希望你明白這一點,也許你開始實際的正確的軌道上。

如果你想將inject_decorator變成一個裝飾器,和/或混合到你的類裝飾器中,祝你好運,大部分的努力工作已經完成。

+0

非常感謝您解釋,我得到了:) –