如何在方法的所有装饰器的基础上应用 class 装饰器
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__
属性联系到他们:
>>> 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
function constructor (found in the 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
这是因为decorator
在prefix
中形成的闭包有两个元素,一个是前缀字符串p
,第二个是实际函数,inner
被嵌套在里面,期望它们都出现在它的闭包中。解决这个问题需要更多的代码来分析和重建细节。
无论如何,这个解释花了很多时间和文字,所以我希望你能理解这一点,也许能让你走上真正正确的轨道。
如果你想把 inject_decorator
变成装饰器,and/or 将它混合到你的 class 装饰器中,祝你好运,大部分艰苦的工作已经完成。
所有方法我都是用这种方式装饰的
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__
属性联系到他们:
>>> 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
function constructor (found in the 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
这是因为decorator
在prefix
中形成的闭包有两个元素,一个是前缀字符串p
,第二个是实际函数,inner
被嵌套在里面,期望它们都出现在它的闭包中。解决这个问题需要更多的代码来分析和重建细节。
无论如何,这个解释花了很多时间和文字,所以我希望你能理解这一点,也许能让你走上真正正确的轨道。
如果你想把 inject_decorator
变成装饰器,and/or 将它混合到你的 class 装饰器中,祝你好运,大部分艰苦的工作已经完成。