防止在同一函数上重复使用装饰器

2 投票
4 回答
3360 浏览
提问于 2025-04-15 14:58

我有一个装饰器:

from functools import wraps
def d(f):
    @wraps(f)
    def wrapper(*args,**kwargs):
        print 'Calling func'
        return f(*args,**kwargs)
    return wrapper

我想防止它对同一个函数进行两次装饰,也就是说,想避免像下面这样的情况:

@d
@d
def f():
   print 2

我能想到的唯一解决办法是用一个字典来存储已经被装饰器装饰过的函数,如果再试着装饰一个已经在字典里的函数,就抛出一个异常。如果你有更好的主意,请告诉我...

4 个回答

0

Noam,关于func_code这个属性,你应该使用co_name。下面的代码中,唯一改变的就是在d()函数定义的前面加了两行。

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áš Lalinský的方法,他使用了一个明确指定的属性,直接附加在函数对象上。这种方法可能更好,因为“包装器”的名字可能在其他地方也会用到……

2

我也来提一个我的解决方案:

首先,创建一个新的装饰器:

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)

现在,任何用这个装饰器装饰的装饰器,只能对一个函数进行一次装饰:

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

我会把信息存储在函数内部。这样做有个风险,就是如果有多个装饰器使用了同一个变量,可能会发生冲突。不过如果只是你自己的代码,应该能避免这个问题。

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

还有一个选择可以这样做:

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 这个属性,你就不知道这个函数被装饰成什么样了。

撰写回答