我正在使用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)
有没有我忽视的选项?如果可能的话,我不想再用手写所有的签名。
答案 0 :(得分:1)
您可能需要在reST文件中手动override the signature。
很难找到更好的答案。 Autodoc导入它所记录的模块,因此执行所有模块级代码(包括默认函数参数)。
更新
我刚才意识到还有另一种选择。您可以通过将签名包含在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
中将保留源代码中的默认值。