Reputation: 10451
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.
Upvotes: 5
Views: 675
Reputation: 59118
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
.
Upvotes: 1
Reputation: 30937
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',)
Upvotes: 0
Reputation: 801
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)
Upvotes: 0