[Python-Dev] The decorator module

Michele Simionato michele.simionato at gmail.com
Fri May 6 17:05:07 CEST 2005


On 5/6/05, Guido van Rossum <gvanrossum at gmail.com> wrote:
> [Michele]
> > Honestly, I don't care, since "eval" happens only once at decoration time.
> > There is no "eval" overhead at calling time, so I do not expect to have
> > problems. I am waiting for volunteers to perform profiling and
> > performance analysis ;)
> 
> Watch out. I didn't see the code referred to, but realize that eval is
> *very* expensive on some other implementations of Python (Jython and
> IronPython). Eval should only be used if there is actual user-provided
> input that you don't know yet when your module is compiled; not to get
> around some limitation in the language there are usually ways around
> that, and occasionally we add one, e.g. getattr()).

I actually posted the code on c.l.p. one month ago asking if there was
a way to avoid "eval", but I had no answer. So, let me repost the code
here and see if somebody comes out with a good solution.
It is only ~30 lines long (+ ~30 of comments & docstrings)

## I suggest you uncomment the 'print lambda_src' statement in _decorate
## to understand what is going on.

import inspect

def _signature_gen(func, rm_defaults=False):
    argnames, varargs, varkwargs, defaults = inspect.getargspec(func)
    argdefs = defaults or ()
    n_args = func.func_code.co_argcount
    n_default_args = len(argdefs)
    n_non_default_args = n_args - n_default_args    
    non_default_names = argnames[:n_non_default_args]
    default_names = argnames[n_non_default_args:]
    for name in non_default_names:
        yield "%s" % name
    for i, name in enumerate(default_names):
        if rm_defaults:
            yield name
        else:
            yield "%s = arg[%s]" % (name, i) 
    if varargs:
        yield "*%s" % varargs
    if varkwargs:
        yield "**%s" % varkwargs

def _decorate(func, caller):
    signature = ", ".join(_signature_gen(func))
    variables = ", ".join(_signature_gen(func, rm_defaults=True))   
    lambda_src = "lambda %s: call(func, %s)" % (signature, variables)
    # print lambda_src # for debugging
    evaldict = dict(func=func, call=caller, arg=func.func_defaults or ())
    dec_func = eval(lambda_src, evaldict)
    dec_func.__name__ = func.__name__
    dec_func.__doc__ = func.__doc__
    dec_func.__dict__ = func.__dict__ # copy if you want to avoid sharing
    return dec_func

class decorator(object):
    """General purpose decorator factory: takes a caller function as
    input and returns a decorator. 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
    ... def f(): pass
    >>> f()
    Calling 'f'
    """
    def __init__(self, caller):
        self.caller = caller
    def __call__(self, func):
        return _decorate(func, self.caller)

Michele Simionato


More information about the Python-Dev mailing list