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

问题描述 投票:8回答:2

如何用修饰功能文档中的真实签名替换*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

到目前为止,一切都很好。我还希望我的包含[Sphinx]正确记录的myfunc的库。但是,如果我通过以下方式在我的狮身人面像html页面中包含函数:

.. automodule:: mymodule
    :members: myfunc

它实际上将显示为:

myfunc(* args,** kwargs)

晦涩加法

  • 参数:
    • foo:吧!
    • bar:bla bla
  • 返回:foo + bar

如何去除标题中的通用myfunc(*args, **kwargs)?应该用myfunc(foo = 42,bar = 43)代替。如何更改狮身人面像或装饰器mywrapper,以使默认关键字参数保留在文档中?

编辑

如已指出,此问题以前曾被问过,但答案并没有那么大的帮助。

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

python decorator python-sphinx python-decorators
2个回答
5
投票

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

# 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取代,该装饰器绝对不执行任何操作,只是返回原始功能。


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

© www.soinside.com 2019 - 2024. All rights reserved.