【问题标题】:sphinx.ext.autodoc: Keeping names of constants in signaturesphinx.ext.autodoc:在签名中保留常量的名称
【发布时间】:2011-08-29 10:16:44
【问题描述】:

我正在使用 Sphinx 的自动文档功能来记录我的 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)

?

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

【问题讨论】:

    标签: python python-sphinx autodoc


    【解决方案1】:

    自 Sphinx 4.0 版以来,有一个新的配置选项 (autodoc_preserve_defaults)。设置

    autodoc_preserve_defaults = True
    

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

    【讨论】:

      【解决方案2】:

      您可能必须在 reST 文件中手动 override the signature

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

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


      更新:

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

      【讨论】:

      【解决方案3】:

      您可以从 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 导入:

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

      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
      

      【讨论】:

      • @mzjn 是的,但是移植到 Python 2 应该不难。
      猜你喜欢
      • 1970-01-01
      • 2022-08-26
      • 2015-02-01
      • 2016-12-24
      • 1970-01-01
      • 2021-11-24
      • 1970-01-01
      • 2014-01-07
      • 1970-01-01
      相关资源
      最近更新 更多