31

我正在尝试使用 Sphinx 来记录我的 Python 类。我使用自动文档这样做:

.. autoclass:: Bus
   :members:

虽然它正确地为我的方法获取文档字符串,但那些被装饰的:

    @checkStale
    def open(self):
        """
        Some docs.
        """
        # Code

@checkStale存在

def checkStale(f):
    @wraps(f)
    def newf(self, *args, **kwargs):
        if self._stale:
            raise Exception
        return f(self, *args, **kwargs)
    return newf

有一个不正确的原型,例如open(*args, **kwargs).

我怎样才能解决这个问题?我的印象是使用@wraps会解决这种事情。

4

6 回答 6

15

我对 celery @task 装饰器也有同样的问题。

您还可以通过将正确的函数签名添加到您的 rst 文件来解决此问题,如下所示:

.. autoclass:: Bus
    :members:

    .. automethod:: open(self)
    .. automethod:: some_other_method(self, param1, param2)

它仍然会自动记录非装饰器成员。

这在http://www.sphinx-doc.org/en/master/ext/autodoc.html#directive-automodule的 sphinx 文档中有所提及——搜索“如果方法的签名被装饰师。”

就我而言,我必须使用 autofunction 在 django 应用程序的 tasks.py 模块中指定我的 celery 任务的签名:

.. automodule:: django_app.tasks
    :members:
    :undoc-members:
    :show-inheritance:

    .. autofunction:: funct1(user_id)
    .. autofunction:: func2(iterations)
于 2013-03-28T22:11:40.287 回答
14

扩展我的评论:

您是否尝试过使用装饰器包并将 @decorator 放在 checkStale 上?我在使用带有装饰功能的 epydoc 时遇到了类似的问题。

正如您在评论中所问的那样,装饰器包不是标准库的一部分。

您可以使用类似以下的代码(未经测试)回退:

try:
    from decorator import decorator
except ImportError:
    # No decorator package available. Create a no-op "decorator".
    def decorator(f):
        return f
于 2010-09-12T19:52:47.407 回答
3

在 1.1 版中添加,您现在可以通过在文档字符串的第一行提供自定义值来覆盖方法签名。

http://sphinx-doc.org/ext/autodoc.html#confval-autodoc_docstring_signature

@checkStale
def open(self):
    """
    open()
    Some docs.
    """
    # Code
于 2012-11-06T20:34:43.837 回答
2

添加'.__文档__':

def checkStale(f):
    @wraps(f)
    def newf(self, *args, **kwargs):
       if self._stale:
          raise Exception
       return f(self, *args, **kwargs)
    newf.__doc__ = f.__doc__
    return newf

并在装饰功能上添加:

@checkStale
def open(self):
    """
    open()
    Some docs.
    """
    # Code
于 2020-02-10T15:46:21.467 回答
1

如果您特别坚持不添加另一个依赖项,这里有一个代码片段,它通过注入文档字符串与常规检查器一起工作。除非有充分的理由不添加另一个模块,否则它非常老套,并不真正推荐,但就是这样。

# inject the wrapped functions signature at the top of a docstring
args, varargs, varkw, defaults = inspect.getargspec(method)
defaults = () if defaults is None else defaults
defaults = ["\"{}\"".format(a) if type(a) == str else a for a in defaults]
l = ["{}={}".format(arg, defaults[(idx+1)*-1]) if len(defaults)-1 >= idx else arg for idx, arg in enumerate(reversed(list(args)))]
if varargs: allargs.append('*' + varargs)
if varkw: allargs.append('**' + varkw)
doc = "{}({})\n{}".format(method.__name__, ', '.join(reversed(l)), method.__doc__)
wrapper.__doc__ = doc
于 2013-03-14T15:58:58.620 回答
0

更新:这可能是“不可能”干净地完成的,因为 sphinx 使用函数的代码对象来生成其函数签名。但是,由于您使用的是 sphinx,因此有一个可行的解决方法。

这很 hacky,因为它在 sphinx 运行时有效地禁用了装饰器,但它确实有效,所以它是一个实用的解决方案。

起初我沿着构建新types.CodeType对象的路线走,以替换包装器的func_code代码对象成员,这是 sphinx 在生成签名时使用的。

co_varnames我能够通过沿着路线或尝试从原始函数中交换代码对象的,co_nlocals等成员来对python 进行段错误,虽然很吸引人,但它太复杂了。

下面的解决方案,虽然是一个笨重的重锤,但也很简单=)

方法如下:在 sphinx 内部运行时,设置一个装饰器可以检查的环境变量。在装饰器内部,当检测到 sphinx 时,根本不做任何装饰,而是返回原始函数。

在你的 sphinx conf.py 中:

import os
os.environ['SPHINX_BUILD'] = '1'

然后这是一个带有测试用例的示例模块,它显示了它的外观:

import functools
import os
import types
import unittest


SPHINX_BUILD = bool(os.environ.get('SPHINX_BUILD', ''))


class StaleError(StandardError):
    """Custom exception for staleness"""
    pass


def check_stale(f):
    """Raise StaleError when the object has gone stale"""

    if SPHINX_BUILD:
        # sphinx hack: use the original function when sphinx is running so that the
        # documentation ends up with the correct function signatures.
        # See 'SPHINX_BUILD' in conf.py.
        return f

    @functools.wraps(f)
    def wrapper(self, *args, **kwargs):
        if self.stale:
            raise StaleError('stale')

        return f(self, *args, **kwargs)
    return wrapper


class Example(object):

    def __init__(self):
        self.stale = False
        self.value = 0

    @check_stale
    def get(self):
        """docstring"""
        return self.value

    @check_stale
    def calculate(self, a, b, c):
        """docstring"""
        return self.value + a + b + c


class TestCase(unittest.TestCase):

    def test_example(self):

        example = Example()
        self.assertEqual(example.get(), 0)

        example.value = 1
        example.stale = True
        self.assertRaises(StaleError, example.get)

        example.stale = False
        self.assertEqual(example.calculate(1, 1, 1), 4)


if __name__ == '__main__':
    unittest.main()
于 2011-10-14T01:20:14.707 回答