开发者

sphinx.ext.autodoc: Keeping names of constants in signature

I'm using S开发者_开发知识库phinx's autodoc feature to document my API.

Example:

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

The generated documentation now shows the following signature:

do_something(msg, option='default')

How can I tell Sphinx to keep the name of the constant value i.e.

do_something(msg, option=DEFAULT_OPTION)

?

Is there an option I have overlooked? If at all possible, I'd like NOT to write all signature by hand again.


Since version 4.0 of Sphinx there is a new configuration option (autodoc_preserve_defaults). Setting

autodoc_preserve_defaults = True

in your conf.py will preserve the default values as they are in the source code.


You probably have to override the signature by hand in the reST file.

It's hard to come up with a better answer. Autodoc imports the modules it documents, so all module-level code (including default function arguments) is executed.

See also these similar questions: here and here.


Update:

I just realized that there is another option. You can override the signature by including it as the very first line of the docstring. See the documentation of the autodoc_docstring_signature configuration variable, and this answer.


You can get the signature with constant names from an AST and "unparse" it back to Python code.

Put this in your conf.py file:

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)

And this in some unparser.py file, importable from conf.py:

Note: This "unparser" probably has many bugs.

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜