views:

129

answers:

4

I have a decorator:

from 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...

A: 

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

gnibbler
That's cool, but it will only tell me whether this function has been decorated before or not, but not whether it has been decorated with this specific decorator.
noam
It will tell you it has been decorated before, but not which decorator
gnibbler
A: 

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...

mjv
+2  A: 

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.

Lukáš Lalinský
I was just thinking that, but i used `hasattr(f,'_decorated_with_d')` It won't work if there is another decorator in bewteen though
gnibbler
If you control all the decorators, you can use set `_decorated_with`, get it from the `f` function, add `d` to it, and store it back to the new wrapper.
Lukáš Lalinský
Well we already know how to detect if there is any wrapper, so combine the two techniques and you can refuse to wrap a decorated function if it has no _decorated_with set on it
gnibbler
+1  A: 

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
You can store the function itself (`f`) in `__called`. Using names can always end up being a problem.
Lukáš Lalinský
are you sure? I think the function's signature will be different once it's decorated the first time
noam
Well, by `f` I mean the `self.__f` attribute in `DecorateOnce`, which is the decorator.
Lukáš Lalinský