在两个python装饰器之间传递变量

时间:2013-07-19 14:44:18

标签: python decorator chaining

是否有办法在应用于同一函数的两个python装饰器之间传递变量?目标是让其中一个装饰者知道另一个也被应用了。我需要类似下面例子中的decobar_present():

def decobar(f):
    def wrap():
        return f() + "bar"
    return wrap

def decofu(f):
    def wrap():
        print decobar_present() # Tells me whether decobar was also applied
        return f() + "fu"
    return wrap

@decofu
@decobar
def important_task():
    return "abc"

更一般地说,我希望能够根据是否也应用了decobar来修改decofu的行为。

5 个答案:

答案 0 :(得分:1)

您可以在应用decobar时将该功能添加到“注册表”,然后检查注册表以确定是否已将decobar应用于该功能。这种方法需要保留原始函数的__module____name__属性(在包装函数上使用functools.wraps)。

import functools

class decobar(object):
    registry = set()

    @classmethod
    def _func_key(cls, f):
        return '.'.join((f.__module__, f.func_name))

    @classmethod
    def present(cls, f):
        return cls._func_key(f) in cls.registry

    def __call__(self, f):
        self.registry.add(self._func_key(f))

        @functools.wraps(f)
        def wrap():
            return f() + "bar"
        return wrap

# Make the decorator singleton
decobar = decobar()

def decofu(f):
    @functools.wraps(f)
    def wrap():
        print decobar.present(f) # Tells me whether decobar was also applied
        return f() + "fu"
    return wrap

@decofu
@decobar
def important_task():
    return "abc"

使用一个类来实现decobar,因为它将registrypresent()保留在一个命名空间中(感觉更清晰,IMO)

答案 1 :(得分:0)

虽然可以做一些事情,比如操纵stack trace,但我认为,你最好创建一个函数decofubar,并尽可能多地合并“fu”和“bar”尽可能。至少,它会使您的代码更清晰,更明显。

答案 2 :(得分:0)

每个装饰器都会包装另一个函数。传递给decofu()的函数是decobar()装饰器的结果。

只需测试decobar包装器的特定特征,只要您能够识别包装器:

def decobar(f):
    def wrap():
        return f() + "bar"
    wrap.decobar = True
    return wrap

def decofu(f):
    def wrap():
        print 'decobar!' if getattr(f, 'decobar') else 'not decobar'
        return f() + "fu"
    return wrap

我在包装函数上使用了一个任意属性,但你可以尝试测试一个名称(不是那么明确),签名(或许使用inspect.getargspec())等等。

仅限于直接包装

一般来说,你不想像所有这些一样紧密地装饰装饰器。制定出不同的解决方案,仅依赖于函数签名或返回值。

答案 3 :(得分:0)

您可以在f中为wrap(或更确切地说decobar)分配旗帜,就像这样

def decobar(f):
    def wrap():
        return f() + "bar"
    wrap.decobar_applied = True
    return wrap

def decofu(f):
    def wrap():
        if hasattr(f, 'decobar_applied') and f.decobar_applied:
            print decobar_present() # Tells me whether decobar was also applied
        return f() + "fu"
    return wrap

@decofu
@decobar
def important_task():
    return "abc"

答案 4 :(得分:0)

要在两个python装饰器之间传递变量,可以使用装饰函数的关键字参数字典。在第二个装饰器中调用函数之前,不要忘记从那里弹出添加的参数。

    def decorator1(func):
        def wrap(*args, **kwargs):
            kwargs['cat_says'] = 'meow'
            return func(*args, **kwargs)
        return wrap

    def decorator2(func):
        def wrap(*args, **kwargs):
            print(kwargs.pop('cat_says'))
            return func(*args, **kwargs)
        return wrap


    class C:
        @decorator1
        @decorator2
        def spam(self, a, b, c, d=0):
            print("Hello, cat! What's your favourite number?")
            return a + b + c + d

    x=C()
    print(x.spam(1, 2, 3, d=7))