Prevent decorator from being used twice on the same function in python
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.
精彩评论