Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 850573b

Browse files
bpo-37995: Add an option to ast.dump() to produce a multiline output. (GH-15631)
1 parent 92709a2 commit 850573b

File tree

5 files changed

+118
-12
lines changed

5 files changed

+118
-12
lines changed

Doc/library/ast.rst

+12-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ and classes for traversing abstract syntax trees:
319319
node = YourTransformer().visit(node)
320320

321321

322-
.. function:: dump(node, annotate_fields=True, include_attributes=False)
322+
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None)
323323

324324
Return a formatted dump of the tree in *node*. This is mainly useful for
325325
debugging purposes. If *annotate_fields* is true (by default),
@@ -329,6 +329,17 @@ and classes for traversing abstract syntax trees:
329329
numbers and column offsets are not dumped by default. If this is wanted,
330330
*include_attributes* can be set to true.
331331

332+
If *indent* is a non-negative integer or string, then the tree will be
333+
pretty-printed with that indent level. An indent level
334+
of 0, negative, or ``""`` will only insert newlines. ``None`` (the default)
335+
selects the single line representation. Using a positive integer indent
336+
indents that many spaces per level. If *indent* is a string (such as ``"\t"``),
337+
that string is used to indent each level.
338+
339+
.. versionchanged:: 3.9
340+
Added the *indent* option.
341+
342+
332343
.. seealso::
333344

334345
`Green Tree Snakes <https://greentreesnakes.readthedocs.io/>`_, an external documentation resource, has good

Doc/whatsnew/3.9.rst

+8
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ New Modules
109109
Improved Modules
110110
================
111111

112+
ast
113+
---
114+
115+
Added the *indent* option to :func:`~ast.dump` which allows it to produce a
116+
multiline indented output.
117+
(Contributed by Serhiy Storchaka in :issue:`37995`.)
118+
119+
112120
threading
113121
---------
114122

Lib/ast.py

+34-11
Original file line numberDiff line numberDiff line change
@@ -96,43 +96,66 @@ def _convert(node):
9696
return _convert(node_or_string)
9797

9898

99-
def dump(node, annotate_fields=True, include_attributes=False):
99+
def dump(node, annotate_fields=True, include_attributes=False, *, indent=None):
100100
"""
101101
Return a formatted dump of the tree in node. This is mainly useful for
102102
debugging purposes. If annotate_fields is true (by default),
103103
the returned string will show the names and the values for fields.
104104
If annotate_fields is false, the result string will be more compact by
105105
omitting unambiguous field names. Attributes such as line
106106
numbers and column offsets are not dumped by default. If this is wanted,
107-
include_attributes can be set to true.
107+
include_attributes can be set to true. If indent is a non-negative
108+
integer or string, then the tree will be pretty-printed with that indent
109+
level. None (the default) selects the single line representation.
108110
"""
109-
def _format(node):
111+
def _format(node, level=0):
112+
if indent is not None:
113+
level += 1
114+
prefix = '\n' + indent * level
115+
sep = ',\n' + indent * level
116+
else:
117+
prefix = ''
118+
sep = ', '
110119
if isinstance(node, AST):
111120
args = []
121+
allsimple = True
112122
keywords = annotate_fields
113123
for field in node._fields:
114124
try:
115125
value = getattr(node, field)
116126
except AttributeError:
117127
keywords = True
118128
else:
129+
value, simple = _format(value, level)
130+
allsimple = allsimple and simple
119131
if keywords:
120-
args.append('%s=%s' % (field, _format(value)))
132+
args.append('%s=%s' % (field, value))
121133
else:
122-
args.append(_format(value))
134+
args.append(value)
123135
if include_attributes and node._attributes:
124-
for a in node._attributes:
136+
for attr in node._attributes:
125137
try:
126-
args.append('%s=%s' % (a, _format(getattr(node, a))))
138+
value = getattr(node, attr)
127139
except AttributeError:
128140
pass
129-
return '%s(%s)' % (node.__class__.__name__, ', '.join(args))
141+
else:
142+
value, simple = _format(value, level)
143+
allsimple = allsimple and simple
144+
args.append('%s=%s' % (attr, value))
145+
if allsimple and len(args) <= 3:
146+
return '%s(%s)' % (node.__class__.__name__, ', '.join(args)), not args
147+
return '%s(%s%s)' % (node.__class__.__name__, prefix, sep.join(args)), False
130148
elif isinstance(node, list):
131-
return '[%s]' % ', '.join(_format(x) for x in node)
132-
return repr(node)
149+
if not node:
150+
return '[]', True
151+
return '[%s%s]' % (prefix, sep.join(_format(x, level)[0] for x in node)), False
152+
return repr(node), True
153+
133154
if not isinstance(node, AST):
134155
raise TypeError('expected AST, got %r' % node.__class__.__name__)
135-
return _format(node)
156+
if indent is not None and not isinstance(indent, str):
157+
indent = ' ' * indent
158+
return _format(node)[0]
136159

137160

138161
def copy_location(new_node, old_node):

Lib/test/test_ast.py

+62
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,68 @@ def test_dump(self):
645645
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)], type_ignores=[])"
646646
)
647647

648+
def test_dump_indent(self):
649+
node = ast.parse('spam(eggs, "and cheese")')
650+
self.assertEqual(ast.dump(node, indent=3), """\
651+
Module(
652+
body=[
653+
Expr(
654+
value=Call(
655+
func=Name(id='spam', ctx=Load()),
656+
args=[
657+
Name(id='eggs', ctx=Load()),
658+
Constant(value='and cheese', kind=None)],
659+
keywords=[]))],
660+
type_ignores=[])""")
661+
self.assertEqual(ast.dump(node, annotate_fields=False, indent='\t'), """\
662+
Module(
663+
\t[
664+
\t\tExpr(
665+
\t\t\tCall(
666+
\t\t\t\tName('spam', Load()),
667+
\t\t\t\t[
668+
\t\t\t\t\tName('eggs', Load()),
669+
\t\t\t\t\tConstant('and cheese', None)],
670+
\t\t\t\t[]))],
671+
\t[])""")
672+
self.assertEqual(ast.dump(node, include_attributes=True, indent=3), """\
673+
Module(
674+
body=[
675+
Expr(
676+
value=Call(
677+
func=Name(
678+
id='spam',
679+
ctx=Load(),
680+
lineno=1,
681+
col_offset=0,
682+
end_lineno=1,
683+
end_col_offset=4),
684+
args=[
685+
Name(
686+
id='eggs',
687+
ctx=Load(),
688+
lineno=1,
689+
col_offset=5,
690+
end_lineno=1,
691+
end_col_offset=9),
692+
Constant(
693+
value='and cheese',
694+
kind=None,
695+
lineno=1,
696+
col_offset=11,
697+
end_lineno=1,
698+
end_col_offset=23)],
699+
keywords=[],
700+
lineno=1,
701+
col_offset=0,
702+
end_lineno=1,
703+
end_col_offset=24),
704+
lineno=1,
705+
col_offset=0,
706+
end_lineno=1,
707+
end_col_offset=24)],
708+
type_ignores=[])""")
709+
648710
def test_dump_incomplete(self):
649711
node = ast.Raise(lineno=3, col_offset=4)
650712
self.assertEqual(ast.dump(node),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added the *indent* option to :func:`ast.dump` which allows it to produce a
2+
multiline indented output.

0 commit comments

Comments
 (0)