Python中的装饰器类

39

我想构建可用作装饰器的类,并保持以下原则不变:

  1. 应该可以将多个这样的类装饰器叠加在一个函数上。
  2. 结果函数名指针应与没有装饰器的相同函数无法区分,除了可能是其类型/类别不同。
  3. 装饰器的顺序不应该有影响,除非特定的装饰器强制规定。也就是说,独立的装饰器可以以任何顺序应用。

这是为一个Django项目,我现在正在处理的具体情况是该方法需要两个装饰器,并且看起来像普通的Python函数:

@AccessCheck
@AutoTemplate
def view(request, item_id) {}

@AutoTemplate将函数更改为仅返回一个字典以在上下文中使用,而不是返回HttpResponse。使用RequestContext,并从方法名称和模块推断模板名称。

@AccessCheck基于item_id对用户进行额外检查。

我猜只是为了正确获取构造函数并复制适当的属性,但这些属性是哪些?

以下装饰器无法按我所述工作:

class NullDecl (object):
    def __init__ (self, func):
        self.func = func
    def __call__ (self, * args):
        return self.func (*args)

如下代码所示:

@NullDecl
@NullDecl
def decorated():
    pass

def pure():
    pass

# results in set(['func_closure', 'func_dict', '__get__', 'func_name',
# 'func_defaults', '__name__', 'func_code', 'func_doc', 'func_globals'])
print set(dir(pure)) - set(dir(decorated));
此外,尝试在NullDecl构造函数中添加“print func.name”,它将适用于第一个装饰器,但不适用于第二个 - 因为名称会丢失。
eduffy的答案进行了改进,看起来效果相当不错:
class NullDecl (object):
    def __init__ (self, func):
        self.func = func
        for n in set(dir(func)) - set(dir(self)):
            setattr(self, n, getattr(func, n))

    def __call__ (self, * args):
        return self.func (*args)
    def __repr__(self):
        return self.func

1
+1 因为我不确定为什么有人会对这个问题投反对票。这是一个好问题:大多数关于装饰器的问题在 SO 上并没有使用类作为装饰器。无论一个人对于使用类而不是函数持何种看法,这都是一个有效的问题,并且可能完全适用于使用情况。 - Jarret Hardie
3个回答

24

一个什么也不做的装饰器类看起来像这样:

class NullDecl (object):
   def __init__ (self, func):
      self.func = func
      for name in set(dir(func)) - set(dir(self)):
        setattr(self, name, getattr(func, name))

   def __call__ (self, *args):
      return self.func (*args)

然后,您可以正常地应用它:

@NullDecl
def myFunc (x,y,z):
   return (x+y)/z

我加入了自己的修复,所以现在它可以正常工作了。感谢您的帮助,通常从基础开始是有帮助的。 - Staale
7
也许在定义def __call__(self, *args):时,还可以添加一个地方来传递**kwargs参数(以及后面调用self.func时的传递)? - Ron Klein
如何在使用functool.wraps时使用它? - theannouncer

10

8
为了创建一个装饰器,使其可以将函数包装起来,使它们与原始函数无法区分,可以使用 functools.wraps
例如:

def mydecorator(func):
    @functools.wraps(func):
    def _mydecorator(*args, **kwargs):
        do_something()
        try:
            return func(*args, **kwargs)
        finally:
            clean_up()
    return _mydecorator

# ... and with parameters
def mydecorator(param1, param2):
    def _mydecorator(func):
        @functools.wraps(func)
        def __mydecorator(*args, **kwargs):
            do_something(param1, param2)
            try:
                return func(*args, **kwargs)
            finally:
                clean_up()
        return __mydecorator
    return _mydecorator

我个人更喜欢使用函数来创建装饰器,而不是类。

装饰器的顺序如下:


@d1
@d2
def func():
    pass

# is equivalent to
def func():
    pass

func = d1(d2(func))

我更喜欢使用类,但我想这是由于我的Java背景。我记得有人建议为装饰器使用类,但我认为那是错误的建议。 - Staale
2
如果你想使用类,我不确定你要把@functools.wraps放在哪里。我觉得将函数作为装饰器更符合Python的风格,而且代码更短。 - codeape
在您的参数示例中,“func”从哪里来?它不在mydecorator()的参数列表中... - Aaron Digulla
你应用@decorator(param1, param2) def func: pass - decorator(param1, param2) 返回实际用于装饰的函数。这里有三个嵌套函数层级。 - Staale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接