這肯定是一個壞的想法,但你想做的事可以做在一定程度上,這需要花費很多時間來解釋。首先,不要將裝飾器看作語法糖,而是將它們視爲它們的本質:一個函數(即閉包),其中存在一個函數。現在,這是出路,假設我們有一個函數:
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_operation
是inject(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'
看起來我們終於得到了我們所需要的。雖然這並不能完全回答你確切的問題,但是這開始走向正確的軌道,但已經非常多毛。
現在的實際問題:這將確保一個裝飾功能最內(最終)給定的前原可調用的,未經修飾的功能的功能 - 即對於一個給定的target
和f(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
變成一個裝飾器,和/或混合到你的類裝飾器中,祝你好運,大部分的努力工作已經完成。
您可能想考慮如何將代碼從'test_2 = login_testuser(other(test_2))'轉換爲'test_2 = other(login_testuser(test_2))'。我的建議是,你想到另一種避免你測試中的'other'裝飾器的方法。雖然有許多方法可以操縱裝飾器形成的閉合,但它會非常混亂。 – metatoaster
@metatoaster。你能否給我一些想法,我該如何做到這一點在關閉。我有很多方法,所以不可能重複。 –
我建議您編輯標題以反映您正在做什麼的真實意圖,例如「將裝飾器注入其已裝飾的原始功能」。或者這個效果的東西。結果比我想象的要難得多。 – metatoaster