这绝对是一个不好的想法,但你想做的事情某种程度上是可以完成的,而且需要花费很多时间来解释。首先,不要把装饰器看成语法糖,而应该将它们看作它们真正所代表的:一个函数(即闭包)和一个存在其中的函数。现在这个问题解决了,假设我们有一个函数:
def operation(a, b):
print('doing operation')
return a + b
简单来说,它会做到这一点。
>>> hi = operation('hello', 'world')
doing operation
>>> print(hi)
helloworld
现在定义一个装饰器,在调用其内部函数之前和之后打印一些内容(相当于您想稍后修饰的other
装饰器):
现在定义一个装饰器,在调用其内部函数之前和之后打印一些内容(相当于您想稍后修饰的
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
。 (有关 Dragons 的内容请参见以下分割线后面的部分)
首先,构造一个函数,通过将闭包深度调用(以便 f
只包含原始的 operation
调用),并将其与由 inject(f)
产生的代码混合来有效地调用 inject(operation)
:
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.
"""
decorators = []
while f.__closure__:
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:
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'
现在尝试注入 粗体
装饰器,如下所示:
>>> 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
转换为一个装饰器,并/或将其混合到类装饰器中,请祝你好运,大部分的工作已经完成了。
test_2 = login_testuser(other(test_2))
转换为test_2 = other(login_testuser(test_2))
。我的建议是,你可以考虑另一种方法来避免在测试中使用other
装饰器。虽然有办法可以操作装饰器形成的闭包,但这会变得非常混乱。 - metatoaster