如何确保在Python中应用函数装饰器的顺序?

时间:2012-09-04 02:58:48

标签: python decorator decorator-chaining

一些装饰器只应在最外层使用。

增加原始功能并添加配置参数的装饰器就是一个例子。

from functools import wraps

def special_case(f):
    @wraps(f)
    def _(a, b, config_x=False):
        if config_x:
           print "Special case here"
           return
        return f(a, b)

如何避免这样的装饰器被另一个装饰器装饰?

修改

让每个人都试图申请新的装饰者担心应用程序顺序真是令人作呕。

那么,有可能避免这种情况吗?是否可以在不引入新参数的情况下添加配置选项?

2 个答案:

答案 0 :(得分:5)

没有办法阻止它被装饰。你只需记录它需要最后应用并告诉人们不要在另一个装饰器中使用它。

编辑回复您的编辑:在Python 3中,您可以为您的函数设置keyword-only argument。这大大减少了变更对功能的现有使用的影响。不幸的是,这仅适用于Python 3。

最终,将装饰器应用于函数只意味着将装饰函数作为参数传递给另一个函数。函数(或任何对象)无法知道 它是作为参数传递的,更不用说它传递给它的内容了。您无法了解以后装饰器的原因与在f(g(x))之类的普通函数调用中的原因相同,函数g无法知道稍后将由f调用它

这是编写装饰器很棘手的原因之一。依赖于大量使用将明确参数传递给其包装函数的装饰器的代码(因为你的传递ab)本身就是脆弱的。幸运的是,很多时候你可以编写一个使用*args**kwargs的装饰器,这样它就可以将它不使用的所有参数传递给装饰函数。

如果有人接受你提供的代码,并编写另一个明确只接受ab作为参数的装饰器,然后将装饰函数调用为f(a, b, True),那就是他们自己的错如果失败了他们应该知道他们使用的其他装饰器可能已经改变了函数签名。

答案 1 :(得分:1)

通常情况下,当编写一个通用的装饰器时,人们不会限制它所包装的函数的参数的数量或名称。

那里的大多数装饰器接受列表o位置参数,并将关键字参数作为其包装器的参数,并将这些参数传递给装饰函数:

def deco(func):
   def wrapper(*args, **kwargs):
       ... decorator stuff here ...
       return func(*args, **kwargs)

因此,如果装饰者要接收一个应该“消耗”的参数 - 就像你提到的config_x一样,你所要做的就是记录它,将它作为关键字参数,并选择它来自kwargs。为了避免参数上的名称冲突,例如,可以使用装饰器自己的名称或其他不同的名称为此参数名称添加前缀:

def deco(func):
   def wrapper(*args, **kwargs):
       if "deco_config_x" in kwargs):
           config_x = kwargs.pop(deco_config_x)
       ... decorator stuff here ...
       return func(*args, **kwargs)

这样,装饰器可以放在“装饰器堆栈”的任何位置 - 它将选择发送给它的参数,而下面的参数不会得到任何陌生的参数。唯一的要求是你的函数和装饰器作为一个整体juts让他们不知道要通过的关键字参数。