[英]How to apply class decorator at base of all decorators on methods
我正在用這種方式裝飾所有方法
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()"
這當然是一個壞主意,但你想做的事情可以在某種程度上完成,這需要花費大量時間來解釋。 首先,不要將裝飾器視為語法糖,而應將它們視為它們的真實含義:一個函數(即一個閉包),其中存在一個函數。 現在這已經不在了,假設我們有一個功能:
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__
屬性與他們__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
。 (休息后跟隨龍)
首先,構造一個有效調用inject(operation)
的函數,通過將閉包調深(使f
只包含原始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
為了測試這個,我們使用典型的用例 - 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
這只是因為prefix
的decorator
形成的閉包有兩個元素,一個是前綴字符串p
,第二個是實際函數,而inner
嵌套在里面,期望它們都存在於其閉包內。 解決這個問題需要更多代碼來分析和重建細節。
無論如何,這個解釋需要相當多的時間和文字,所以我希望你能理解這一點,也許會讓你開始實際的正確軌道。
如果你想將inject_decorator
變成裝飾器,和/或將它混合到你的類裝飾器中,祝你好運,大部分艱苦的工作已經完成。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.