1

有没有办法在应用于同一函数的两个 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 的行为。

4

5 回答 5

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因为它保留在一个命名空间中(感觉有点干净,IMO)registrypresent()

于 2013-07-19T15:06:27.390 回答
0

虽然可以执行诸如操作堆栈跟踪之类的操作,但我认为您最好简单地创建一个函数decofubar并尽可能多地合并“fu”和“bar”。至少,它会使你的代码更清晰、更明显。

于 2013-07-19T14:50:51.027 回答
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()可能使用)等。

这仅限于直接包装

一般来说,你不想像这一切那样紧密地耦合装饰器。制定一个不同的解决方案,只依赖于函数签名或返回值。

于 2013-07-19T14:53:33.287 回答
0

您可以像这样将标志分配给f(或更确切地说wrapdecobar

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"
于 2013-07-19T14:54:38.483 回答
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))
于 2018-04-27T06:33:20.853 回答