Decorating functions without losing their signatures

Rotwang sg552 at hotmail.co.uk
Wed Apr 3 21:44:40 EDT 2013


On 03/04/2013 02:05, Rotwang wrote:
> [...]
>
> After thinking about it for a while I've come up with the following
> abomination:
>
> import inspect
>
> def sigwrapper(sig):
>    if not isinstance(sig, inspect.Signature):
>      sig = inspect.signature(sig)
>    def wrapper(f):
>      ps = 'args = []\n\t\t'
>      ks = 'kwargs = {}\n\t\t'
>      for p in sig.parameters.values():
>        if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD):
>          ps = '%sargs.append(%s)\n\t\t' % (ps, p.name)
>        elif p.kind == p.VAR_POSITIONAL:
>          ps = '%sargs.extend(%s)\n\t\t' % (ps, p.name)
>        elif p.kind == p.KEYWORD_ONLY:
>          ks = '%skwargs[%r] = %s\n\t\t' % (ks, p.name, p.name)
>        elif p.kind == p.VAR_KEYWORD:
>          ks = '%skwargs.update(%s)\n\t\t' % (ks, p.name)
>      loc = {'wrapped': f}
>      defstring = ('def wrapouter(wrapped = wrapped):'
>                   '\n\tdef wrapinner%s:'
>                   '\n\t\t%s%sreturn wrapped(*args, **kwargs)'
>                   '\n\treturn wrapinner' % (sig, ps, ks))
>      exec(defstring, f.__globals__, loc)
>      return loc['wrapouter']()
>    return wrapper

Oops! Earlier I found out the hard way that this fails when the 
decorated function has arguments called 'args' or 'kwargs'. Here's a 
modified version that fixes said bug, but presumably not the many others 
I haven't noticed yet:

def sigwrapper(sig):
   if not isinstance(sig, inspect.Signature):
     sig = inspect.signature(sig)
   n = 0
   while True:
     pn = 'p_%i' % n
     kn = 'k_%i' % n
     if pn not in sig.parameters and kn not in sig.parameters:
       break
     n += 1
   ps = '%s = []\n\t\t' % pn
   ks = '%s = {}\n\t\t' % kn
   for p in sig.parameters.values():
     if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD):
       ps = '%s%s.append(%s)\n\t\t' % (ps, pn, p.name)
     elif p.kind == p.VAR_POSITIONAL:
       ps = '%s%s.extend(%s)\n\t\t' % (ps, pn, p.name)
     elif p.kind == p.KEYWORD_ONLY:
       ks = '%s%s[%r] = %s\n\t\t' % (ks, kn, p.name, p.name)
     elif p.kind == p.VAR_KEYWORD:
       ks = '%s%s.update(%s)\n\t\t' % (ks, kn, p.name)
   defstring = ('def wrapouter(wrapped = wrapped):'
                '\n\tdef wrapinner%s:'
                '\n\t\t%s%sreturn wrapped(*%s, **%s)'
                '\n\treturn wrapinner' % (sig, ps, ks, pn, kn))
   def wrapper(f):
     loc = {'wrapped': f}
     exec(defstring, f.__globals__, loc)
     return loc['wrapouter']()
   return wrapper



More information about the Python-list mailing list