views:

95

answers:

3

How can I keep help strings in functions to be visible after applying a decorator?

Right now the doc string is (partially) replaced with that of the inner function of the decorator.

def deco(fn):
    def x(*args, **kwargs):
        return fn(*args, **kwargs)
    x.func_doc = fn.func_doc
    x.func_name = fn.func_name
    return x

@deco
def y(a, b):
    """This is Y"""
    pass

def z(c, d):
    """This is Z"""
    pass

help(y) # 1
help(z) # 2

In the Y function, required arguments aren't shown in the help. The user may assume it takes any arguments, while actually it doesn't.

y(*args, **kwargs) <= y(a, b) is desired
    This is Y

z(c, d)
    This is Z

I use help() and dir() a lot, since it's faster than pdf manuals, and want to make reliable document strings for my library and tools, but this is an obstacle.

A: 
import functools

def deco(fn):
    @functools.wraps(fn)
    def x(*args, **kwargs):
        fn.func_doc
        return fn(*args, **kwargs)
    return x
del-boy
*args, **kwargs are still there
culebrón
+5  A: 

give the decorator module a peek. i believe it does exactly what you want.

In [1]: from decorator import decorator
In [2]: @decorator
   ...: def say_hello(f, *args, **kwargs):
   ...:     print "Hello!"
   ...:     return f(*args, **kwargs)
   ...: 
In [3]: @say_hello
   ...: def double(x):
   ...:     return 2*x
   ...:

and info says "double(x)" in it.

Autoplectic
+1  A: 

What you're requesting is very hard to do "properly", because help gets the function signature from inspect.getargspec which in turn gets it from introspection which cannot directly be fooled -- to do it "properly" would mean generating a new function object on the fly (instead of a simple wrapper function) with the right argument names and numbers (and default values). Extremely hard, advanced, black-magic bytecode hacking required, in other words.

I think it may be easier to do it by monkeypatching (never a pleasant prospect, but sometimes the only way to perform customization tasks that are otherwise so difficult as to prove almost impossible, like the one you require) -- replace the real inspect.getargspec with your own lookalike function which uses a look-aside table (mapping the wrapper functions you generate to the wrapped functions' argspecs and otherwise delegating to the real thing).

import functools
import inspect

realgas = inspect.getargspec

lookaside = dict()

def fakegas(f):
    if f in lookaside:
        return lookaside[f]
    return realgas(f)

inspect.getargspec = fakegas

def deco(fn):
    @functools.wraps(fn)
    def x(*args, **kwargs):
        return fn(*args, **kwargs)
    lookaside[x] = realgas(fn)
    return x

@deco
def x(a, b=23):
  """Some doc for x."""
  return a + b

help(x)

This prints, as required:

Help on function x in module __main__:

x(a, b=23)
    Some doc for x.
(END)
Alex Martelli