在 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__
会更容易。
【讨论】:
我想我可以使用inspect
和inspect.getargspec(myfunc)
提取函数的默认参数?!但仍将这些信息预先添加到文档中不会消除错误的标题。
我为这个问题创建了一个新的github问题:github.com/sphinx-doc/sphinx/issues/1711以上是关于在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数的主要内容,如果未能解决你的问题,请参考以下文章
Sphinx autodoc - 装饰器和 ReadTheDocs