簡體   English   中英

如何在方法的所有裝飾器的基礎上應用類裝飾器

[英]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_operationinject(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'

看起來我們終於得到了我們需要的東西。 雖然這並沒有完全回答你的確切問題,但這開始了正確的軌道,但已經非常毛茸茸。


現在針對實際問題:一個函數將確保裝飾器函數在給定的原始未修飾函數之前是最內部(最終)可調用的 - 即對於給定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

為了測試這個,我們使用典型的用例 - 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

這只是因為prefixdecorator形成的閉包有兩個元素,一個是前綴字符串p ,第二個是實際函數,而inner嵌套在里面,期望它們都存在於其閉包內。 解決這個問題需要更多代碼來分析和重建細節。


無論如何,這個解釋需要相當多的時間和文字,所以我希望你能理解這一點,也許會讓你開始實際的正確軌道。

如果你想將inject_decorator變成裝飾器,和/或將它混合到你的類裝飾器中,祝你好運,大部分艱苦的工作已經完成。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM