简体   繁体   English

如何在方法的所有装饰器的基础上应用类装饰器

[英]How to apply class decorator at base of all decorators on methods

I am using this way of decorating all 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()"

This is working but it applies at the top , if i have other decorators. 这是有效的,但它适用于顶部,如果我有其他装饰器。

I mean 我的意思是

Now the result is 现在的结果是

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

But i want 但我想要

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

This is most certainly a bad idea, but what you want to do can be done in some extent, and this is going to take a lot of time to explain. 这当然是一个主意,但你想做的事情可以在某种程度上完成,这需要花费大量时间来解释。 First off, rather than thinking of decorators as a syntax sugar, think of them as what they really are: a function (that is a closure) with a function that exist inside it. 首先,不要将装饰器视为语法糖,而应将它们视为它们的真实含义:一个函数(即一个闭包),其中存在一个函数。 Now this is out of the way, supposed we have a function: 现在这已经不在了,假设我们有一个功能:

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

Simply it will do this 它只会这样做

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

Now define a decorator that prints something before and after calling its inner function (equivalent to the other decorator that you want to decorator later): 现在定义一个装饰器,在调用它的内部函数之前和之后打印一些东西(相当于你以后要装饰的other装饰器):

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

With that, build a new function and decorator 有了它,建立一个新的功能和装饰

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

Remembering, this is basically equivalent to o_operation = other(operation) 记住,这基本上等同于o_operation = other(operation)

Run this to ensure it works: 运行此命令以确保其有效:

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

Finally, the final decorator you want to call immediately before operation but not d_operation , but with your existing code it results in this: 最后,您希望在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

Run the above: 运行以上:

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

As mentioned decorators are really closures and hence that's why it's possible to have items inside that are effectively instanced inside. 正如前面提到的那样,装饰器真的是封闭的,因此这就是为什么内部的物品可以在里面有效实例化的原因。 You can reach them by going through the __closure__ attribute: 您可以通过__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

See how this effectively calls the function inside the injected closure directly, as if that got unwrapped. 看看它如何有效地直接调用injected闭包内的函数,就好像它被解包一样。 What if that closure can be replaced with the one that did the injection? 如果关闭可以用注射剂替换怎么办? For all of our protection, __closure__ and cell.cell_contents are read-only. 对于我们所有的保护, __closure__cell.cell_contents是只读的。 What needs to be done is to construct completely new functions with the intended closures by making use of the FunctionType function constructor (found in the types module) 需要做的是通过使用FunctionType函数构造函数(在types模块中找到)构造具有预期闭包的全新函数

Back to the problem. 回到问题所在。 Since what we have now is: 因为我们现在拥有的是:

i_o_operation = inject(other(operation))

And what we want is 而我们想要的是

o_i_operation = other(inject(operation))

We effectively have to somehow strip the call to other from i_o_operation and somehow wrap it around with inject to produce o_i_operation . 我们实际上必须以某种方式从i_o_operation剥离对其other的调用,并以某种方式用inject包装它来生成o_i_operation (Dragons follows after the break) (休息后跟随龙)


First, construct a function that effectively calls inject(operation) by taking the closure to level deep (so that f will contain just the original operation call) but mix it with the code produced by inject(f) : 首先,构造一个有效调用inject(operation)的函数,通过将闭包调深(使f只包含原始operation调用),但将其与inject(f)生成的代码混合:

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

Since i_o_operation is the result of inject(f) we can take that code to produce a new function. 由于i_o_operationinject(f)的结果,我们可以使用该代码生成一个新函数。 The globals is a formality that's required, and finally take the closure of the nested level, and the first part of the function is produced. globals是必需的形式,最后采用嵌套级别的闭包,并生成函数的第一部分。 Verify that the other is not called. 验证是否未调用other

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

Neat. 整齐。 However we still want the other to be wrapped outside of this to finally produce o_i_operation . 但是我们仍然希望将other包裹在其中以最终生成o_i_operation We do need to somehow put this new function we produced in a closure, and a way to do this is to create a surrogate function that produce one 我们确实需要以某种方式将我们生成的这个新函数放在一个闭包中,一种方法是创建一个代理函数来生成一个

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

And simply use it to construct and extract our closure 并简单地用它来构造和提取我们的闭包

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

Call this: 叫这个:

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

Looks like we finally got what we need. 看起来我们终于得到了我们需要的东西。 While this doesn't exactly answer your exact problem, this started down the right track but is already pretty hairy. 虽然这并没有完全回答你的确切问题,但这开始了正确的轨道,但已经非常毛茸茸。


Now for the actual problem: a function that will ensure a decorator function be the most inner (final) callable before a given original, undecorated function - ie for a given target and a f(g(...(callable)) , we want to emulate a result that gives f(g(...(target(callable)))) . This is the code: 现在针对实际问题:一个函数将确保装饰器函数在给定的原始未修饰函数之前是最内部(最终)可调用的 - 即对于给定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

To test this, we use a typical example use-case - html tags. 为了测试这个,我们使用典型的用例 - 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

Running the test. 运行测试。

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

Our target is to inject the underline decorator (specifically the u(hi) callable) into the most inner closure. 我们的目标是将underline装饰器(特别是u(hi)可调用的)注入到最内部的闭包中。 This can be done like so, with the function we have defined above: 这可以这样做,使用我们上面定义的函数:

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

Works with undecorated functions: 使用未修饰的功能:

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

A major assumption was made for this first-cut version of the rewriter, which is that all decorators in the chain only have a closure length of one, that one element being the function being decorated with. 这个重写器的第一个版本的一个主要假设是,链中的所有装饰器只有一个闭包长度,一个元素是装饰的函数。 Take this decorator for instance: 拿这个装饰器为例:

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

Example usage: 用法示例:

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

Now try to inject a bold decorator like so: 现在尝试注入一个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

This is simply because the closure formed by decorator within prefix has two elements, one being the prefix string p and the second being the actual function, and inner being nested inside that expects both those to be present inside its closure. 这只是因为prefixdecorator形成的闭包有两个元素,一个是前缀字符串p ,第二个是实际函数,而inner嵌套在里面,期望它们都存在于其闭包内。 Resolving that will require more code to analyse and reconstruct the details. 解决这个问题需要更多代码来分析和重建细节。


Anyway, this explanation took quite a bit of time and words, so I hope you understand this and maybe get you started on the actual right track. 无论如何,这个解释需要相当多的时间和文字,所以我希望你能理解这一点,也许会让你开始实际的正确轨道。

If you want to turn inject_decorator into a decorator, and/or mix it into your class decorator, best of luck, most of the hard work is already done. 如果你想将inject_decorator变成装饰器,和/或将它混合到你的类装饰器中,祝你好运,大部分艰苦的工作已经完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM