在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数

Posted

技术标签:

【中文标题】在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数【英文标题】:Preserve default arguments of wrapped/decorated Python function in Sphinx documentation 【发布时间】:2015-04-06 15:46:20 【问题描述】:

如何将*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)

模糊添加

参数: foo:吧! 酒吧:bla bla 返回: 富+酒吧

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

编辑

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

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

【问题讨论】:

Python Sphinx autodoc and decorated members 的可能重复项 抱歉没有找到问题。是的,它是完全相同的。但恐怕答案不是很有帮助。我不想繁琐地手动将所有装饰函数添加到我的 sphinx 文档中。 【参考方案1】:

我想出了一个 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,它什么也不做,只是简单地返回原始函数。

【讨论】:

这……太可怕了。我当然希望 Sphinx 不会在自己的代码库中的任何地方调用 functools.wraps() 这在conf.py 中对我不起作用,尽管如果我将它放在我对包装器的定义之前(相当无用)。 conf.py 中是否有一个特殊的地方需要这个? 我把它放在conf.py的末尾。但我想你必须小心在导入任何其他使用functools.wraps 的模块之前将其包含在from functools import wraps 方面。【参考方案2】:

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

这是 Python 中一个已知的复杂问题 - 以至于最近的版本 - 不仅包括 Python 3,而且 Python 2.7 在装饰的类上包含了一个 __wrapped__ 属性,可以正确使用 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 ) - 但无论如何,对于一个幼稚的形式,您可以编写另一个版本的“包装”:

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__ 会更容易。

【讨论】:

我想我可以使用inspectinspect.getargspec(myfunc) 提取函数的默认参数?!但仍将这些信息预先添加到文档中不会消除错误的标题。 我为这个问题创建了一个新的github问题:github.com/sphinx-doc/sphinx/issues/1711

以上是关于在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数的主要内容,如果未能解决你的问题,请参考以下文章

如何让 sphinx 识别装饰的 python 函数

Sphinx autodoc - 装饰器和 ReadTheDocs

当一个方法中有两个装饰器时,为啥 sphinx autodoc 会输出一个装饰器的文档字符串?

Sphinx 文档没有列出修饰函数的参数

交叉引用装饰器

Python:异常装饰器。如何保留堆栈跟踪