Python 如何定义为函数/方法调用提供插值文档字符串的装饰器

Python 如何定义为函数/方法调用提供插值文档字符串的装饰器,python,decorator,Python,Decorator,我还没有足够好的装饰师来做这件事。。。是否可以定义一个decoratorlive_doc,它允许我在方法或函数调用后获得一个插入的文档字符串,并用实际参数和返回值填充 @live_doc("f was called with %d, %s and returned %d") def f(x, y): x + len(y) 在以下代码之后: f(3, "marty") d = f.doc d应该是“f被称为3,“marty”,返回8”。我宁愿在访问f.doc之前不构建字符串,但肯定需要

我还没有足够好的装饰师来做这件事。。。是否可以定义一个decoratorlive_doc,它允许我在方法或函数调用后获得一个插入的文档字符串,并用实际参数和返回值填充

@live_doc("f was called with %d, %s and returned %d")
def f(x, y):
  x + len(y)
在以下代码之后:

f(3, "marty")

d = f.doc 
d应该是“f被称为3,“marty”,返回8”。我宁愿在访问f.doc之前不构建字符串,但肯定需要将调用args和返回值存储在某个地方 t>代码>):

我用过这个

相当于

func = decomaker(argA, argB, ...)(func)
我想到了这个:

#!/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
它维护函数的文档字符串,因此您仍然可以调用
help(foo)
来阅读其说明。每次调用时,它都会使用参数和结果(或引发的异常)更新该docstring:

打印文件__ 富文档 还没打电话 >>>傅(2) 4. >>>打印foo.\u文件__ 富文档 最后使用args调用:(2,),{} 最后一次返回:4 >>>傅(1) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 文件“/tmp/foo.py”,第11行,换行 retval=func(*args,**kwargs) 文件“/tmp/foo.py”,第27行,在foo中 提升值错误('baz') ValueError:baz >>>打印foo.\u文件__ 富文档 最后使用args调用:(1,),{} 失败并引发:ValueError('baz',)
这里有一个比较通用的解决方案,可以处理原始的docstring 作为模板,并维护有关修饰函数的其他信息 (喜欢它的名字):

在首次调用
f
之前,交互式解释器中的
help(f)
将为您提供:

Help on function f in module __main__:

f(*args, **kwargs)
    %(name)s was called with %(signature)s and returned %(ret_val)r.
呼叫后,您将获得:

f(*args, **kwargs)
    f was called with 3, 'marty' and returned 8.
或者使用更通用的功能,显示
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.
显然,您可以在
wrapper
内的模板中创建想要使用的任何变量

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)
Help on function f in module __main__:

f(*args, **kwargs)
    %(name)s was called with %(signature)s and returned %(ret_val)r.
f(*args, **kwargs)
    f was called with 3, 'marty' and returned 8.
@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.