Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a decorator that will provide an interpolated doc string for a function/method call

I'm not good enough with decorators yet to do this ... Is it possible to define a decorator live_doc that allows me to get an interpolated doc string after a method or function call, filled in with the actual arguments and return value.

@live_doc("f was called with %d, %s and returned %d")
def f(x, y):
  x + len(y)

After the code below:

f(3, "marty")

d = f.doc 

d should be "f was called with 3, "marty", and returned 8". I would prefer to not build up the strings until f.doc is accessed, but would surely need to squirrel away the call args & return value somewhere.

like image 977
Des Avatar asked Nov 12 '22 16:11

Des


1 Answers

Here's a somewhat generalised solution that will treat your original docstring as a template, and maintain other information about the decorated function (like its name):

from functools import wraps

def live_doc(func):
    template = func.__doc__
    @wraps(func)
    def wrapper(*args, **kwargs):
        ret_val = func(*args, **kwargs)
        args_pretty = ", ".join(repr(a) for a in args)
        kw_pretty = ", ".join("%s=%r" % (k, v) for k, v in kwargs.items())
        signature = ", ".join(x for x in (args_pretty, kw_pretty) if x)
        name =  func.__name__
        wrapper.__doc__ = template % locals()
        return ret_val
    return wrapper

@live_doc
def f(x, y):
    """%(name)s was called with %(signature)s and returned %(ret_val)r."""
    return x + len(y)

Before f is first called, help(f) in the interactive interpreter gives you:

Help on function f in module __main__:

f(*args, **kwargs)
    %(name)s was called with %(signature)s and returned %(ret_val)r.

After it's called, you get:

f(*args, **kwargs)
    f was called with 3, 'marty' and returned 8.

Or with a more general function, showing off kwargs:

@live_doc
def q(*args, **kwargs):
    """%(name)s was called with %(signature)s and returned %(ret_val)r."""
    return len(args) + len(kwargs)

>>> q(1, 2, 3, a=7, b="foo")
5
>>> help(q)
q(*args, **kwargs)
    q was called with 1, 2, 3, a=7, b='foo' and returned 5.

Obviously you could create whatever variables you wanted to use in the template inside wrapper.

like image 163
Zero Piraeus Avatar answered Nov 15 '22 04:11

Zero Piraeus