sphinx.ext.autodoc:将常量名称保留在签名中

发布于 2024-12-02 01:15:41 字数 399 浏览 0 评论 0原文

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

示例:

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

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

do_something(msg, option='default')

如何告诉 Sphinx 保留常量值的名称 ie

do_something(msg, option=DEFAULT_OPTION)

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

I'm using Sphinx'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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

鹿童谣 2024-12-09 01:15:41

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

autodoc_preserve_defaults = True

将保留源代码中的默认值。

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.

小清晰的声音 2024-12-09 01:15:41

您可能必须在 reST 文件中手动覆盖签名

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

另请参阅这些类似的问题:此处此处< /a>.


更新:

我刚刚意识到还有另一种选择。您可以通过将签名作为文档字符串的第一行来覆盖签名。请参阅 autodoc_docstring_signature 配置变量的文档,以及 这个答案

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.

醉生梦死 2024-12-09 01:15:41

您可以从 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)

并将其放入某个可从 conf.py 导入的 unparser.py 文件中:

注意:这个“解析器”可能有很多错误。

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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文