在 Sphinx 文档中保留 wrapped/decorated Python 函数的默认参数

Preserve default arguments of wrapped/decorated Python function in Sphinx documentation

如何在修饰函数的文档中用真正的签名替换*args**kwargs

假设我有以下装饰器和装饰函数:

import functools

def mywrapper(func):
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        print('Wrapping Ho!')
        return func(*args, **kwargs)
    return new_func

@mywrapper
def myfunc(foo=42, bar=43):
    """Obscure Addition

    :param foo: bar!
    :param bar: bla bla
    :return: foo + bar

    """
    return foo + bar

因此,调用 print(myfunc(3, 4)) 给我们:

Wrapping Ho!
7

到目前为止一切顺利。我还希望包含 myfunc 的库用 Sphinx 正确记录。 但是,如果我通过以下方式将函数包含在我的 sphinx html 页面中:

.. automodule:: mymodule
    :members: myfunc

它实际上会显示为:

myfunc(*args, **kwargs)

晦涩的加法

如何去掉标题中的通用 myfunc(*args, **kwargs)?这应该被替换为 myfunc(foo=42, bar=43)。我如何更改 sphinx 或我的装饰器 mywrapper 以便在文档中保留默认关键字参数?

编辑:

正如所指出的,这个问题之前已经被问过,但答案并不是很有帮助。

但是,我有一个想法,想知道这是否可能。 Sphinx 是否设置了一些环境变量来告诉我的模块它实际上是由 Sphinx 导入的?如果是这样,我可以简单地 monkey-patch 我自己的包装器。如果我的模块是由 Sphinx 导入的,我的包装器 return 原始函数而不是包装它们。因此,签名得以保留。

你通常不能。 那是因为在包装函数中用作参数的变量名甚至不存在于包装函数中 - 所以 Sphinx 不知道它们。

这是 Python 中的一个已知复杂问题 - 如此之多以至于最近的版本 - 不仅包括 Python 3,而且 Python 2.7 都包含一个 __wrapped__ 属性在 class 上正确使用 functools.wraps 装饰 - 这样一来,在检查装饰函数后,可以通过查看 __wrapped__ 了解实际的包装函数。不幸的是,Sphinxs 忽略了 __wrapped__,而是显示包装函数的信息。

所以,肯定要做的一件事就是将此作为错误报告给 Sphinx 项目本身 - 它应该考虑 __wrapped__

同时解决这个问题的方法是更改​​包装函数以实际包含有关包装的更多信息 - 比如它的签名 - 所以你可以为你的项目编写另一个函数来代替 "functools.wraps" ,它就是这样做的:在 其文档字符串的函数签名(如果有)。 不幸的是,检索早于 3.3 的 Python 中的函数签名是棘手的——(对于 3.3 和更新的版本,检查 https://docs.python.org/3/library/inspect.html#inspect-signature-object )——但是无论如何,对于一个天真的形式,你可以编写另一个版本的 "wraps"沿:

def wraps(original_func):
   wrap_decorator = functools.wraps(original_func)
   def re_wrapper(func):
       wrapper = wrap_decorator(func)
       poorman_sig = original_func.__code__.co_varnames[
                         :original_func.__code__.co_argcount]
       wrapper.__doc__ = "{} ({})\n\n{}".format (
            original_func.__name__, ", ".join(poorman_sig),
            wrapper.__doc__) 
       return wrapper
   return re_wrapper

并用它代替 "functools.wraps"。它至少会添加一行参数名称(但不是默认值)作为文档中的第一行。

---嗯..也许在正确完成此操作之前修补 Sphinx 以使用 __wrapped__ 会更容易。

我想出了一个 functools.wraps 的猴子补丁。 因此,我只是将其添加到项目文档的 sphinx source 文件夹中的 conf.py 脚本中:

# Monkey-patch functools.wraps
import functools

def no_op_wraps(func):
    """Replaces functools.wraps in order to undo wrapping.

    Can be used to preserve the decorated function's signature
    in the documentation generated by Sphinx.

    """
    def wrapper(decorator):
        return func
    return wrapper

functools.wraps = no_op_wraps

因此,当通过 make html 构建 html 页面时,functools.wraps 被这个装饰器 no_op_wraps 替换,它只做 return原始功能。