Hi all,

Here's a Python problem I've come up against and my crappy solution. Hopefully someone here can suggest something better. I want to decorate a bunch of functions with different signatures; for example, I might want to add some keyword-only arguments to all functions that return instances of a particular class so that the caller can create instances with additional attributes. So I do something like this:

import functools

def mydecorator(f):
    @functools.wraps(f)
    def wrapped(*args, attribute = None, **kwargs):
        result = f(*args, **kwargs)
        result.attribute = attribute
        return result
    return wrapped

@mydecorator
def f(x, y = 1, *a, z = 2, **k):
        return something

The problem with this is, when I subsequently type 'f(' in IDLE, the signature prompt that appears is not very useful; it looks like this:

(*args, attribute=None, **kwargs)

whereas I'd like it to look like this:

(x, y=1, *a, z=2, attribute=None, **k)


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

The function sigwrapper() may be passed an inspect.Signature object sig (or function, if that function has the right signature) and returns a decorator that gives any function the signature sig. I can then replace my original decorator with something like

def mydecorator(f):
    sig = inspect.signature(f)
    sig = do_something(sig) # add an additional kw-only argument to sig
    @functools.wraps(f)
    @sigwrapper(sig)
    def wrapped(*args, attribute = None, **kwargs):
        result = f(*args, **kwargs)
        result.attribute = attribute
        return result
    return wrapped

It seems to work, but I don't like it. Does anyone know of a better way of doing the same thing?
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to