As I\'ve understood it there are two ways to do a Python decorator, to either use the __call__ of a class or to define and call a function as the decorator. Wha
I mostly agree with jsbueno: there's no one right way. It depends on the situation. But I think def is probably better in most cases, because if you go with class, most of the "real" work is going to be done in __call__ anyway. Also, callables that are not functions are pretty rare (with the notable exception of instantiating a class), and people generally do not expect that. Also, local variables are usually easier for people to keep track of vs. instance variables, simply because they have more limited scope, although in this case, the instance variables are probably only used in __call__ (with __init__ simply copying them from arguments).
I have to disagree with his hybrid approach though. It's an interesting design, but I think it's probably going to confuse the crap out of you or someone else who looks at it a few months later.
Tangent: Regardless of whether you go with class or function, you should use functools.wraps, which itself is meant to be used as a decorator (we must go deeper!) like so:
import functools
def require_authorization(f):
@functools.wraps(f)
def decorated(user, *args, **kwargs):
if not is_authorized(user):
raise UserIsNotAuthorized
return f(user, *args, **kwargs)
return decorated
@require_authorization
def check_email(user, etc):
# etc.
This makes decorated look like check_email e.g. by changing it's func_name attribute.
Anyway, this is usually what I do and what I see other people around me doing, unless I want a decorator factory. In that case, I just add another level of def:
def require_authorization(action):
def decorate(f):
@functools.wraps(f):
def decorated(user, *args, **kwargs):
if not is_allowed_to(user, action):
raise UserIsNotAuthorized(action, user)
return f(user, *args, **kwargs)
return decorated
return decorate
By the way, I would also be on guard against excessive use of decorators, because they can make it really hard to follow stack traces.
One approach for managing hideous stack traces is to have a policy of not substantially changing the behavior of the decoratee. E.g.
def log_call(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
logging.debug('call being made: %s(*%r, **%r)',
f.func_name, args, kwargs)
return f(*args, **kwargs)
return decorated
A more extreme approach for keeping your stack traces sane is for the decorator to return the decoratee unmodified, like so:
import threading
DEPRECATED_LOCK = threading.Lock()
DEPRECATED = set()
def deprecated(f):
with DEPRECATED_LOCK:
DEPRECATED.add(f)
return f
@deprecated
def old_hack():
# etc.
This is useful if the function is called within a framework that knows about the deprecated decorator. E.g.
class MyLamerFramework(object):
def register_handler(self, maybe_deprecated):
if not self.allow_deprecated and is_deprecated(f):
raise ValueError(
'Attempted to register deprecated function %s as a handler.'
% f.func_name)
self._handlers.add(maybe_deprecated)