简体   繁体   中英

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.

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 .

This is my code, (and I felt pretty silly writing it, so I might be doing something wrong, especially in the middle part with t ):

def live_doc(d):
    def f_d(f):
        def f_new(*args):
            r = f(*args)
            t = [a for a in args]
            t.append(r)
            t = tuple(t)
            f_new.doc = d % t
            return r
        return f_new
    return f_d

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

f(1,"hi")
print(f.doc)
// f was called with 1, hi and returned 3

I used from http://www.python.org/dev/peps/pep-0318/ that

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
    pass

is equivalent to

func = decomaker(argA, argB, ...)(func)

I came up with this:

#!/usr/bin/env python

def docme(func):
    def wrap(*args, **kwargs):
        retval = None
        wrap.__doc__ = wrap.__olddoc__ + """

Last called with args: %s, %s
""" % (args, kwargs)
        try:
            retval = func(*args, **kwargs)
            wrap.__doc__ += 'Last returned: %s' % retval
            return retval
        except Exception as exc:
            wrap.__doc__ += 'Failed and raised: %r' % exc
            raise

    wrap.__doc__ = func.__doc__ + '\n\nHas not been called yet'
    wrap.__name__ = func.__name__
    wrap.__olddoc__ = func.__doc__
    return wrap

@docme
def foo(x):
    """foo docs"""
    if x == 1:
        raise ValueError('baz')
    return x * 2

It maintains the function's doc string so you can still call help(foo) to read its instructions. On every call, it updates that docstring with arguments and result (or the exception it raised):

>>> print foo.__doc__
foo docs

Has not been called yet
>>> foo(2)
4
>>> print foo.__doc__
foo docs

Last called with args: (2,), {}
Last returned: 4
>>> foo(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/foo.py", line 11, in wrap
    retval = func(*args, **kwargs)
  File "/tmp/foo.py", line 27, in foo
    raise ValueError('baz')
ValueError: baz
>>> print foo.__doc__
foo docs

Last called with args: (1,), {}
Failed and raised: ValueError('baz',)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM