Python Sphinx autodoc和装饰成员

时间:2010-09-10 17:59:59

标签: python decorator python-sphinx

我正在尝试使用Sphinx来记录我的Python类。我这样做是使用autodoc:

.. 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会解决这类问题。

6 个答案:

答案 0 :(得分: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

答案 1 :(得分:13)

我对芹菜@task装饰器有同样的问题。

您也可以通过在第一个文件中添加正确的函数签名来解决此问题,如下所示:

.. 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 app的tasks.py模块中指定我的celery任务的签名:

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

    .. autofunction:: funct1(user_id)
    .. autofunction:: func2(iterations)

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:1)

添加'.__ doc __':

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

答案 4 :(得分:0)

如果您特别坚持不在此处添加其他依赖项,则可以通过注入docstring来使用常规检查器的代码段。除非有充分的理由不添加另一个模块,否则它非常hackey而且不是真正推荐的,但现在就是。

# 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

答案 5 :(得分:-2)

更新:这可能是“不可能”干净利落的,因为sphinx使用函数的代码对象来生成其函数签名。但是,既然你正在使用sphinx,那么有一种可行的hacky解决方法。

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

首先,我开始构建一个新的types.CodeType对象,以替换包装器的func_code代码对象成员,这是sphinx在生成签名时使用的。

我能够通过沿着路线走下来或尝试从原始函数交换代码对象的co_varnamesco_nlocals等成员来进行段错误,并且在吸引人的时候,它是太复杂了。

以下解决方案,虽然它是一个hacky重锤,也很简单=)

方法如下:在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()