sphinx.ext.autodoc:保持签名中的常量名称

时间:2011-08-29 10:16:44

标签: python python-sphinx

我正在使用Sphinx的autodoc功能来记录我的API。

示例:

DEFAULT_OPTION = 'default'
def do_something(msg, option=DEFAULT_OPTION):
    print msg

生成的文档现在显示以下签名:

do_something(msg, option='default')

如何告诉Sphinx保留常数值的名称,即

do_something(msg, option=DEFAULT_OPTION)

有没有我忽视的选项?如果可能的话,我不想再用手写所有的签名。

3 个答案:

答案 0 :(得分:1)

您可能需要在reST文件中手动override the signature

很难找到更好的答案。 Autodoc导入它所记录的模块,因此执行所有模块级代码(包括默认函数参数)。

另请参阅以下类似问题:herehere


更新

我刚才意识到还有另一种选择。您可以通过将签名包含在docstring的第一行来覆盖签名。请参阅autodoc_docstring_signature配置变量和this answer

的文档

答案 1 :(得分:1)

您可以从AST获取具有常量名称的签名,并将其“解析”回Python代码。

将其放入conf.py文件中:

import ast
import inspect

from unparser import Unparser


unparse = Unparser()

def get_signature_from_ast(app, what, name, obj, options, signature,
                           return_annotation):
    if what in ('class', 'exception', 'function', 'method'):
        remove_args = 0
        if what == 'method':
            remove_args += 1  # Remove self from instance methods.
        while True:
            if inspect.isclass(obj):
                obj = obj.__init__
            elif inspect.ismethod(obj):
                remove_args += 1  # Remove self from instance methods.
                obj = obj.__func__
            elif hasattr(obj, '__wrapped__'):
                obj = obj.__wrapped__
            else:
                break
        filename = sys.modules[obj.__module__].__file__
        with open(filename) as file:
            node = ast.parse(file.read(), filename)
        lineno = obj.__code__.co_firstlineno
        for n in ast.walk(node):
            if isinstance(n, ast.FunctionDef) and n.lineno == lineno:
                signature = '(' + unparse.argspec(n.args, remove_args) + ')'
                if n.returns:
                    return_annotation = unparse.expr(n.returns)
                break
    return signature, return_annotation

def setup(app):
    app.connect('autodoc-process-signature', get_signature_from_ast)

这可以在某个unparser.py文件中导出,可以从conf.py导入:

  

注意:这个“unarser”可能有很多错误。

import ast
from itertools import zip_longest


class _Ast(object):
    """Type that returns a dummy type on failed attribute access.
    Used for backwards compatibility when accessing new types in the :mod:`ast`
    module.
    """
    def __getattribute__(self, attr):
        """Return a type from :mod:`ast` or a dummy type when the attribute
        does not exist in the module.
        """
        return getattr(ast, attr, type(self))

_ast = _Ast()


class Unparser(object):
    """Unparse an AST back to Python code.
    Supports only expressions, up to Python 3.3.
    """
    #: Mapping of AST types to Python code strings.
    ast_symbols = {
        # Boolean binary operators.
        _ast.And: 'and',
        _ast.Or: 'or',
        # Binary operators.
        _ast.Add: '+',
        _ast.Sub: '-',
        _ast.Mult: '*',
        _ast.Div: '/',
        _ast.FloorDiv: '//',
        _ast.Mod: '%',
        _ast.LShift: '<<',
        _ast.RShift: '>>',
        _ast.BitOr: '|',
        _ast.BitAnd: '&',
        _ast.BitXor: '^',
        # Comparison operators.
        _ast.Eq: '==',
        _ast.Gt: '>',
        _ast.GtE: '>=',
        _ast.In: 'in',
        _ast.Is: 'is',
        _ast.IsNot: 'is not',
        _ast.Lt: '<',
        _ast.LtE: '<=',
        _ast.NotEq: '!=',
        _ast.NotIn: 'not in',
        # Unary operators.
        _ast.Invert: '~',
        _ast.Not: 'not',
        _ast.UAdd: '+',
        _ast.USub: '-'
    }

    def args(unparse, args, defaults, remove_args=0, override_args={}):
        """Unparse arguments from an argspec. This can strip out positional
        arguments and replace keyword arguments.
        """
        l = []
        defaults = list(map(unparse.expr, defaults))
        args = list(zip_longest(reversed(args), reversed(defaults)))
        args.reverse()
        for arg, default in args[remove_args:]:
            a = arg.arg
            if a in override_args:
                default = repr(override_args[a])
            if arg.annotation:
                a += ': ' + unparse.expr(arg.annotation)
            if default is not None:
                a += '=' + default
            l.append(a)
        return l

    def argspec(unparse, node, remove_args=0, override_args={}):
        """Unparse an argspec from a function definition. This can strip out
        positional arguments and replace keyword arguments."""
        s = []
        s.extend(unparse.args(node.args, node.defaults,
                              remove_args, override_args))
        if node.vararg or node.kwonlyargs:
            vararg = '*'
            if node.vararg:
                vararg += node.vararg
                if node.varargannotation:
                    vararg += ': ' + unparse.expr(node.varargannotation)
            s.append(vararg)
        s.extend(unparse.args(node.kwonlyargs, node.kw_defaults,
                              override_args=override_args))
        kwarg = node.kwarg
        if kwarg:
            if node.kwargannotation:
                kwarg += ': ' + unparse.expr(node.kwargannotation)
            s.append('**' + kwarg)
        return ', '.join(s)

    def comprehension(unparse, node):
        """Unparse a comprehension."""
        s = ['for', unparse.expr(node.target), 'in', unparse.expr(node.iter)]
        for cond in node.ifs:
            s.extend(('if', cond))
        return ' '.join(s)

    def slice(unparse, node):
        """Unparse a slice."""
        s = ''
        if isinstance(node, _ast.Slice):
            s = []
            if node.lower:
                s.append(unparse.expr(node.lower))
            else:
                s.append('')
            if node.upper:
                s.append(unparse.expr(node.upper))
            else:
                s.append('')
            if node.step:
                s.append(unparse.expr(node.step))
            s = ':'.join(s)
        elif isinstance(node, _ast.ExtSlice):
            s = ', '.join(map(unparse.slice, node.dims))
        elif isinstance(node, _ast.Index):
            s = unparse.expr(node.value)
        return s

    def expr(unparse, node, parenthesise=False):
        """Unparse an expression."""
        s = 'None'
        if isinstance(node, _ast.BoolOp):
            s = []
            for expr in node.values:
                s.append(unparse.expr(expr, parenthesise=True))
            s = (' ' + unparse.ast_symbols[type(node.op)] + ' ').join(s)
        elif isinstance(node, _ast.BinOp):
            s = ' '.join((unparse.expr(node.left, parenthesise=True),
                          unparse.ast_symbols[type(node.op)],
                          unparse.expr(node.right, parenthesise=True)))
        elif isinstance(node, _ast.UnaryOp):
            s = (unparse.ast_symbols[type(node.op)] +
                 unparse.expr(node.operand, parenthesise=True))
        elif isinstance(node, _ast.Lambda):
            s = ('lambda ' + unparse.argspec(node.args) + ': ' +
                 unparse.expr(node.body))
        elif isinstance(node, _ast.IfExp):
            s = ' '.join((unparse.expr(node.body),
                          'if', unparse.expr(node.test),
                          'else', unparse.expr(node.orelse)))
        elif isinstance(node, _ast.Dict):
            s = []
            for key, value in zip(node.keys, node.values):
                s.append(unparse.expr(key) + ': ' + unparse.expr(value))
            s = '{' + ', '.join(s) + '}'
            parenthesise = False
        elif isinstance(node, _ast.Set):
            s = '{' + ', '.join(map(unparse.expr, node.elts)) + '}'
            parenthesise = False
        elif isinstance(node, _ast.ListComp):
            s = [unparse.expr(node.elt)]
            s.extend(map(unparse.comprehension, node.generators))
            s = '[' + ' '.join(map(unparse.expr, node.elts)) + ']'
            parenthesise = False
        elif isinstance(node, _ast.SetComp):
            s = [unparse.expr(node.elt)]
            s.extend(map(unparse.comprehension, node.generators))
            s = '{' + ' '.join(map(unparse.expr, node.elts)) + '}'
            parenthesise = False
        elif isinstance(node, _ast.DictComp):
            s = [unparse.expr(node.key) + ': ' + unparse.expr(node.value)]
            s.extend(map(unparse.comprehension, node.generators))
            s = '{' + ' '.join(map(unparse.expr, node.elts)) + '}'
            parenthesise = False
        elif isinstance(node, _ast.GeneratorExp):
            s = [unparse.expr(node.elt)]
            s.extend(map(unparse.comprehension, node.generators))
            s = '(' + ' '.join(map(unparse.expr, node.elts)) + ')'
            parenthesise = False
        elif isinstance(node, _ast.Yield):
            s = ['yield']
            if node.value:
                s.append(unparse.expr(node.value))
            s = ' '.join(s)
            parenthesise = False
        elif isinstance(node, _ast.YieldFrom):
            s = ['yield from']
            if node.value:
                s.append(unparse.expr(node.value))
            s = ' '.join(s)
            parenthesise = False
        elif isinstance(node, _ast.Compare):
            s = [unparse.expr(node.left, parenthesise=True)]
            for op, operand in zip(node.ops, node.comparators):
                s.append(unparse.ast_symbols[type(op)])
                s.append(unparse.expr(operand, parenthesise=True))
            s = ' '.join(s)
        elif isinstance(node, _ast.Call):
            s = list(map(unparse.expr, node.args))
            if node.starargs:
                s.append('*' + unparse.expr(node.starargs, parenthesise=True))
            for kw in node.keywords:
                s.append(kw.arg + '=' +
                         unparse.expr(kw.value, parenthesise=True))
            if node.kwargs:
                s.append('**' + unparse.expr(node.kwargs, parenthesise=True))
            s = (unparse.expr(node.func, parenthesise=True) +
                 '(' + ', '.join(s) + ')')
            parenthesise = False
        elif isinstance(node, _ast.Num):
            s = repr(node.n)
            parenthesise = False
        elif isinstance(node, (_ast.Str, _ast.Bytes)):
            s = repr(node.s)
            parenthesise = False
        elif isinstance(node, _ast.Ellipsis):
            s = '...'
            parenthesise = False
        elif isinstance(node, _ast.Attribute):
            s = unparse.expr(node.value) + '.' + node.attr
            parenthesise = False
        elif isinstance(node, _ast.Subscript):
            s = (unparse.expr(node.value, parenthesise=True) +
                 '[' + unparse.slice(node.slice) + ']')
            parenthesise = False
        elif isinstance(node, _ast.Starred):
            s = '*' + unparse.expr(node.value)
            parenthesise = False
        elif isinstance(node, _ast.Name):
            s = node.id
            parenthesise = False
        elif isinstance(node, _ast.List):
            s = '[' + ', '.join(map(unparse.expr, node.elts)) + ']'
            parenthesise = False
        elif isinstance(node, _ast.Tuple):
            s = ', '.join(map(unparse.expr, node.elts))
            if len(node.elts) == 1:
                s += ','
            s = '(' + s + ')'
            parenthesise = False
        if parenthesise:
            s = '(' + s + ')'
        return s

答案 2 :(得分:1)

从 Sphinx 4.0 版开始,有一个新的配置选项 (autodoc_preserve_defaults)。设置

autodoc_preserve_defaults = True

在您的 conf.py 中将保留源代码中的默认值。