防止装饰器在python中的同一个函数上使用两次

Prevent decorator from being used twice on the same function in python

我有个装修师:

1
2
3
4
5
6
7
from functools import wraps
def d(f):
    @wraps(f)
    def wrapper(*args,**kwargs):
        print 'Calling func'
        return f(*args,**kwargs)
    return wrapper

我想防止它两次装饰同一个功能,例如,防止:

1
2
3
4
@d
@d
def f():
   print 2

我能想到的唯一可能的解决方案是使用dict来存储修饰器已经修饰的函数,如果要求修饰dict中存在的函数,则会引发异常。如果你有更好的主意…


我还将提出我的解决方案:

首先,创建另一个装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
class DecorateOnce(object):
    def __init__(self,f):
        self.__f=f
        self.__called={} #save all functions that have been decorated
    def __call__(self,toDecorate):
        #get the distinct func name
        funcName=toDecorate.__module__+toDecorate.func_name
        if funcName in self.__called:
            raise Exception('function already decorated by this decorator')
        self.__called[funcName]=1
        print funcName
        return self.__f(toDecorate)

现在,你用这个装饰器装饰的每个装饰师,只会限制自己装饰一次Func:

1
2
3
@DecorateOnce
def decorate(f):
    def wrapper...


我将信息存储在函数本身中。如果多个修饰符决定使用同一个变量,则存在冲突的风险,但是如果它只是您自己的代码,则应该能够避免冲突。

1
2
3
4
5
6
7
8
9
def d(f):
    if getattr(f, '_decorated_with_d', False):
        raise SomeException('Already decorated')
    @wraps(f)
    def wrapper(*args,**kwargs):
        print 'Calling func'
        return f(*args,**kwargs)
    wrapper._decorated_with_d = True
    return wrapper

另一种选择是:

1
2
3
4
5
6
7
8
9
10
11
def d(f):
    decorated_with = getattr(f, '_decorated_with', set())
    if d in decorated_with:
        raise SomeException('Already decorated')
    @wraps(f)
    def wrapper(*args,**kwargs):
        print 'Calling func'
        return f(*args,**kwargs)
    decorated_with.add(d)
    wrapper._decorated_with = decorated_with
    return wrapper

这假定您控制所有使用的装饰器。如果有一个装饰器不复制_decorated_with属性,您将不知道它是用什么装饰的。


noam,func_code使用的财产为co_name。如下所示,所有更改都是d()的def顶部的两行

1
2
3
4
5
6
7
8
def d(f):
   if f.func_code.co_name == 'wrapper':
      return f    #ignore it  (or can throw exception instead...)
   @wraps(f)
   def wrapper(*args, **kwargs):
      print 'calling func'
      return f(*args, **kwargs)
   return wrapper

另外,请参见"luk_"部分。Lalinsky的方法,它使用一个显式定义的附加到函数对象的属性。这可能更可取,因为"包装器"名称可以在其他地方使用…


看看f.func_code,它可以告诉您f是函数还是包装器。