开发者

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

开发者 https://www.devze.com 2022-12-08 18:25 出处:网络
I have a decorator: 开发者_如何学Gofrom functools import wraps def d(f): @wraps(f) def wrapper(*args,**kwargs):

I have a decorator:

开发者_如何学Gofrom functools import wraps
def d(f):
    @wraps(f)
    def wrapper(*args,**kwargs):
        print 'Calling func'
        return f(*args,**kwargs)
    return wrapper

And I want to prevent it from decorating the same function twice, e.g prevent things such as:

@d
@d
def f():
   print 2

Only possible solution I could think of is using a dict to store the functions the decorator has already decorated and raising an exception if asked to decorate a function that exists in the dict. Do tell if you have a better idea...


I'd store the information in the function itself. There is a risk of a conflict if multiple decorators decide to use the same variable, but if it's only your own code, you should be able to avoid it.

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

Another option can be this:

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

This assumes that you control all the decorators used. If there is a decorator that doesn't copy the _decorated_with attribute, you will not know what is it decorated with.


I'll also propose my solution:

first, create another decorator:

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)

Now every decorator you decorate with this decorator, will restrict itself to decorate a func only once:

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


Noam, The property of func_code to use is co_name. See below, all that is changed is two lines at top of d()'s def

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

Also, see for Lukáš Lalinský's approach which uses a explicitly defined property attached to the function object. This may be preferable as the "wrapper" name may be used elsewhere...


Look at f.func_code, it can tell you if f is a function or a wrapper.

0

精彩评论

暂无评论...
验证码 换一张
取 消