## The basic trick is to generate the source code for the decorated function ## with the right signature and to evaluate it. ## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate ## to understand what is going on. __all__ = ["decorator", "update_wrapper", "getinfo"] import inspect, sys def getinfo(func): """ Returns an info dictionary containing: - name (the name of the function : str) - argnames (the names of the arguments : list) - defaults (the values of the default arguments : tuple) - signature (the signature : str) - doc (the docstring : str) - module (the module name : str) - dict (the function __dict__ : str) >>> def f(self, x=1, y=2, *args, **kw): pass >>> info = getinfo(f) >>> info["name"] 'f' >>> info["argnames"] ['self', 'x', 'y', 'args', 'kw'] >>> info["defaults"] (1, 2) >>> info["signature"] 'self, x, y, *args, **kw' """ assert inspect.ismethod(func) or inspect.isfunction(func) regargs, varargs, varkwargs, defaults = inspect.getargspec(func) argnames = list(regargs) if varargs: argnames.append(varargs) if varkwargs: argnames.append(varkwargs) signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, formatvalue=lambda value: "")[1:-1] return dict(name=func.__name__, argnames=argnames, signature=signature, defaults = func.func_defaults, doc=func.__doc__, module=func.__module__, dict=func.__dict__, globals=func.func_globals, closure=func.func_closure) def update_wrapper(wrapper, wrapped, create=False): """ An improvement over functools.update_wrapper. By default it works the same, but if the 'create' flag is set, generates a copy of the wrapper with the right signature and update the copy, not the original. Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module', 'dict', 'defaults'. """ if isinstance(wrapped, dict): infodict = wrapped else: # assume wrapped is a function infodict = getinfo(wrapped) assert not '_wrapper_' in infodict["argnames"], \ '"_wrapper_" is a reserved argument name!' if create: # create a brand new wrapper with the right signature src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict # import sys; print >> sys.stderr, src # for debugging purposes wrapper = eval(src, dict(_wrapper_=wrapper)) try: wrapper.__name__ = infodict['name'] except: # Python version < 2.4 pass wrapper.__doc__ = infodict['doc'] wrapper.__module__ = infodict['module'] wrapper.__dict__.update(infodict['dict']) wrapper.func_defaults = infodict['defaults'] return wrapper # the real meat is here def _decorator(caller, func): infodict = getinfo(func) argnames = infodict['argnames'] assert not ('_call_' in argnames or '_func_' in argnames), \ 'You cannot use _call_ or _func_ as argument names!' src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict dec_func = eval(src, dict(_func_=func, _call_=caller)) return update_wrapper(dec_func, func) def decorator(caller, func=None): """ General purpose decorator factory: takes a caller function as input and returns a decorator with the same attributes. A caller function is any function like this:: def caller(func, *args, **kw): # do something return func(*args, **kw) Here is an example of usage: >>> @decorator ... def chatty(f, *args, **kw): ... print "Calling %r" % f.__name__ ... return f(*args, **kw) >>> chatty.__name__ 'chatty' >>> @chatty ... def f(): pass ... >>> f() Calling 'f' For sake of convenience, the decorator factory can also be called with two arguments. In this casem ``decorator(caller, func)`` is just a shortcut for ``decorator(caller)(func)``. """ if func is None: # return a decorator function return update_wrapper(lambda f : _decorator(caller, f), caller) else: # return a decorated function return _decorator(caller, func) if __name__ == "__main__": import doctest; doctest.testmod()