blob: 720dd48a761b6d4b94873d1e97c7b08f6b37a9c4 [file] [log] [blame]
Georg Brandl0c77a822008-06-10 16:37:50 +00001"""
2 ast
3 ~~~
4
5 The `ast` module helps Python applications to process trees of the Python
6 abstract syntax grammar. The abstract syntax itself might change with
7 each Python release; this module helps to find out programmatically what
8 the current grammar looks like and allows modifications of it.
9
10 An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
11 a flag to the `compile()` builtin function or by using the `parse()`
12 function from this module. The result will be a tree of objects whose
13 classes all inherit from `ast.AST`.
14
15 A modified abstract syntax tree can be compiled into a Python code object
16 using the built-in `compile()` function.
17
18 Additionally various helper functions are provided that make working with
19 the trees simpler. The main intention of the helper functions and this
20 module in general is to provide an easy to use interface for libraries
21 that work tightly with the python syntax (template engines for example).
22
23
24 :copyright: Copyright 2008 by Armin Ronacher.
25 :license: Python License.
26"""
27from _ast import *
28
29
Guido van Rossum495da292019-03-07 12:38:08 -080030def parse(source, filename='<unknown>', mode='exec', *,
Guido van Rossum10b55c12019-06-11 17:23:12 -070031 type_comments=False, feature_version=None):
Georg Brandl0c77a822008-06-10 16:37:50 +000032 """
Terry Reedyfeac6242011-01-24 21:36:03 +000033 Parse the source into an AST node.
34 Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
Guido van Rossumdcfcd142019-01-31 03:40:27 -080035 Pass type_comments=True to get back type comments where the syntax allows.
Georg Brandl0c77a822008-06-10 16:37:50 +000036 """
Guido van Rossumdcfcd142019-01-31 03:40:27 -080037 flags = PyCF_ONLY_AST
38 if type_comments:
39 flags |= PyCF_TYPE_COMMENTS
Guido van Rossum10b55c12019-06-11 17:23:12 -070040 if isinstance(feature_version, tuple):
41 major, minor = feature_version # Should be a 2-tuple.
42 assert major == 3
43 feature_version = minor
44 elif feature_version is None:
45 feature_version = -1
46 # Else it should be an int giving the minor version for 3.x.
Guido van Rossum495da292019-03-07 12:38:08 -080047 return compile(source, filename, mode, flags,
Victor Stinnerefdf6ca2019-06-12 02:52:16 +020048 _feature_version=feature_version)
Georg Brandl0c77a822008-06-10 16:37:50 +000049
50
51def literal_eval(node_or_string):
52 """
53 Safely evaluate an expression node or a string containing a Python
54 expression. The string or node provided may only consist of the following
Éric Araujo2a83cc62011-04-17 19:10:27 +020055 Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
56 sets, booleans, and None.
Georg Brandl0c77a822008-06-10 16:37:50 +000057 """
Georg Brandl0c77a822008-06-10 16:37:50 +000058 if isinstance(node_or_string, str):
59 node_or_string = parse(node_or_string, mode='eval')
60 if isinstance(node_or_string, Expression):
61 node_or_string = node_or_string.body
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020062 def _convert_num(node):
63 if isinstance(node, Constant):
Serhiy Storchaka3f228112018-09-27 17:42:37 +030064 if type(node.value) in (int, float, complex):
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020065 return node.value
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020066 raise ValueError('malformed node or string: ' + repr(node))
67 def _convert_signed_num(node):
68 if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
69 operand = _convert_num(node.operand)
70 if isinstance(node.op, UAdd):
71 return + operand
72 else:
73 return - operand
74 return _convert_num(node)
Georg Brandl0c77a822008-06-10 16:37:50 +000075 def _convert(node):
Victor Stinnerf2c1aa12016-01-26 00:40:57 +010076 if isinstance(node, Constant):
77 return node.value
Georg Brandl0c77a822008-06-10 16:37:50 +000078 elif isinstance(node, Tuple):
79 return tuple(map(_convert, node.elts))
80 elif isinstance(node, List):
81 return list(map(_convert, node.elts))
Georg Brandl492f3fc2010-07-11 09:41:21 +000082 elif isinstance(node, Set):
83 return set(map(_convert, node.elts))
Georg Brandl0c77a822008-06-10 16:37:50 +000084 elif isinstance(node, Dict):
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020085 return dict(zip(map(_convert, node.keys),
86 map(_convert, node.values)))
Victor Stinnerf2c1aa12016-01-26 00:40:57 +010087 elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020088 left = _convert_signed_num(node.left)
89 right = _convert_num(node.right)
90 if isinstance(left, (int, float)) and isinstance(right, complex):
Victor Stinnerf2c1aa12016-01-26 00:40:57 +010091 if isinstance(node.op, Add):
92 return left + right
93 else:
94 return left - right
Serhiy Storchakad8ac4d12018-01-04 11:15:39 +020095 return _convert_signed_num(node)
Georg Brandl0c77a822008-06-10 16:37:50 +000096 return _convert(node_or_string)
97
98
Serhiy Storchaka850573b2019-09-09 19:33:13 +030099def dump(node, annotate_fields=True, include_attributes=False, *, indent=None):
Georg Brandl0c77a822008-06-10 16:37:50 +0000100 """
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300101 Return a formatted dump of the tree in node. This is mainly useful for
102 debugging purposes. If annotate_fields is true (by default),
103 the returned string will show the names and the values for fields.
104 If annotate_fields is false, the result string will be more compact by
105 omitting unambiguous field names. Attributes such as line
Benjamin Petersondcf97b92008-07-02 17:30:14 +0000106 numbers and column offsets are not dumped by default. If this is wanted,
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300107 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.
Georg Brandl0c77a822008-06-10 16:37:50 +0000110 """
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300111 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 = ', '
Georg Brandl0c77a822008-06-10 16:37:50 +0000119 if isinstance(node, AST):
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300120 args = []
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300121 allsimple = True
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300122 keywords = annotate_fields
123 for field in node._fields:
124 try:
125 value = getattr(node, field)
126 except AttributeError:
127 keywords = True
128 else:
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300129 value, simple = _format(value, level)
130 allsimple = allsimple and simple
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300131 if keywords:
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300132 args.append('%s=%s' % (field, value))
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300133 else:
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300134 args.append(value)
Georg Brandl0c77a822008-06-10 16:37:50 +0000135 if include_attributes and node._attributes:
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300136 for attr in node._attributes:
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300137 try:
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300138 value = getattr(node, attr)
Serhiy Storchakae64f9482019-08-29 09:30:23 +0300139 except AttributeError:
140 pass
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300141 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
Georg Brandl0c77a822008-06-10 16:37:50 +0000148 elif isinstance(node, list):
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300149 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
Georg Brandl0c77a822008-06-10 16:37:50 +0000154 if not isinstance(node, AST):
155 raise TypeError('expected AST, got %r' % node.__class__.__name__)
Serhiy Storchaka850573b2019-09-09 19:33:13 +0300156 if indent is not None and not isinstance(indent, str):
157 indent = ' ' * indent
158 return _format(node)[0]
Georg Brandl0c77a822008-06-10 16:37:50 +0000159
160
161def copy_location(new_node, old_node):
162 """
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000163 Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
164 attributes) from *old_node* to *new_node* if possible, and return *new_node*.
Georg Brandl0c77a822008-06-10 16:37:50 +0000165 """
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000166 for attr in 'lineno', 'col_offset', 'end_lineno', 'end_col_offset':
Georg Brandl0c77a822008-06-10 16:37:50 +0000167 if attr in old_node._attributes and attr in new_node._attributes \
168 and hasattr(old_node, attr):
169 setattr(new_node, attr, getattr(old_node, attr))
170 return new_node
171
172
173def fix_missing_locations(node):
174 """
175 When you compile a node tree with compile(), the compiler expects lineno and
176 col_offset attributes for every node that supports them. This is rather
177 tedious to fill in for generated nodes, so this helper adds these attributes
178 recursively where not already set, by setting them to the values of the
179 parent node. It works recursively starting at *node*.
180 """
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000181 def _fix(node, lineno, col_offset, end_lineno, end_col_offset):
Georg Brandl0c77a822008-06-10 16:37:50 +0000182 if 'lineno' in node._attributes:
183 if not hasattr(node, 'lineno'):
184 node.lineno = lineno
185 else:
186 lineno = node.lineno
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000187 if 'end_lineno' in node._attributes:
188 if not hasattr(node, 'end_lineno'):
189 node.end_lineno = end_lineno
190 else:
191 end_lineno = node.end_lineno
Georg Brandl0c77a822008-06-10 16:37:50 +0000192 if 'col_offset' in node._attributes:
193 if not hasattr(node, 'col_offset'):
194 node.col_offset = col_offset
195 else:
196 col_offset = node.col_offset
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000197 if 'end_col_offset' in node._attributes:
198 if not hasattr(node, 'end_col_offset'):
199 node.end_col_offset = end_col_offset
200 else:
201 end_col_offset = node.end_col_offset
Georg Brandl0c77a822008-06-10 16:37:50 +0000202 for child in iter_child_nodes(node):
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000203 _fix(child, lineno, col_offset, end_lineno, end_col_offset)
204 _fix(node, 1, 0, 1, 0)
Georg Brandl0c77a822008-06-10 16:37:50 +0000205 return node
206
207
208def increment_lineno(node, n=1):
209 """
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000210 Increment the line number and end line number of each node in the tree
211 starting at *node* by *n*. This is useful to "move code" to a different
212 location in a file.
Georg Brandl0c77a822008-06-10 16:37:50 +0000213 """
Georg Brandl0c77a822008-06-10 16:37:50 +0000214 for child in walk(node):
215 if 'lineno' in child._attributes:
216 child.lineno = getattr(child, 'lineno', 0) + n
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000217 if 'end_lineno' in child._attributes:
218 child.end_lineno = getattr(child, 'end_lineno', 0) + n
Georg Brandl0c77a822008-06-10 16:37:50 +0000219 return node
220
221
222def iter_fields(node):
223 """
224 Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
225 that is present on *node*.
226 """
227 for field in node._fields:
228 try:
229 yield field, getattr(node, field)
230 except AttributeError:
231 pass
232
233
234def iter_child_nodes(node):
235 """
236 Yield all direct child nodes of *node*, that is, all fields that are nodes
237 and all items of fields that are lists of nodes.
238 """
239 for name, field in iter_fields(node):
240 if isinstance(field, AST):
241 yield field
242 elif isinstance(field, list):
243 for item in field:
244 if isinstance(item, AST):
245 yield item
246
247
248def get_docstring(node, clean=True):
249 """
250 Return the docstring for the given node or None if no docstring can
251 be found. If the node provided does not have docstrings a TypeError
252 will be raised.
Matthias Bussonnier41cea702017-02-23 22:44:19 -0800253
254 If *clean* is `True`, all tabs are expanded to spaces and any whitespace
255 that can be uniformly removed from the second line onwards is removed.
Georg Brandl0c77a822008-06-10 16:37:50 +0000256 """
Yury Selivanov2f07a662015-07-23 08:54:35 +0300257 if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)):
Georg Brandl0c77a822008-06-10 16:37:50 +0000258 raise TypeError("%r can't have docstrings" % node.__class__.__name__)
Serhiy Storchaka08f127a2018-06-15 11:05:15 +0300259 if not(node.body and isinstance(node.body[0], Expr)):
Serhiy Storchaka73cbe7a2018-05-29 12:04:55 +0300260 return None
261 node = node.body[0].value
262 if isinstance(node, Str):
263 text = node.s
264 elif isinstance(node, Constant) and isinstance(node.value, str):
265 text = node.value
266 else:
267 return None
Serhiy Storchaka08f127a2018-06-15 11:05:15 +0300268 if clean:
Victor Stinnerf2c1aa12016-01-26 00:40:57 +0100269 import inspect
270 text = inspect.cleandoc(text)
271 return text
Georg Brandl0c77a822008-06-10 16:37:50 +0000272
273
Ivan Levkivskyi9932a222019-01-22 11:18:22 +0000274def _splitlines_no_ff(source):
275 """Split a string into lines ignoring form feed and other chars.
276
277 This mimics how the Python parser splits source code.
278 """
279 idx = 0
280 lines = []
281 next_line = ''
282 while idx < len(source):
283 c = source[idx]
284 next_line += c
285 idx += 1
286 # Keep \r\n together
287 if c == '\r' and idx < len(source) and source[idx] == '\n':
288 next_line += '\n'
289 idx += 1
290 if c in '\r\n':
291 lines.append(next_line)
292 next_line = ''
293
294 if next_line:
295 lines.append(next_line)
296 return lines
297
298
299def _pad_whitespace(source):
300 """Replace all chars except '\f\t' in a line with spaces."""
301 result = ''
302 for c in source:
303 if c in '\f\t':
304 result += c
305 else:
306 result += ' '
307 return result
308
309
310def get_source_segment(source, node, *, padded=False):
311 """Get source code segment of the *source* that generated *node*.
312
313 If some location information (`lineno`, `end_lineno`, `col_offset`,
314 or `end_col_offset`) is missing, return None.
315
316 If *padded* is `True`, the first line of a multi-line statement will
317 be padded with spaces to match its original position.
318 """
319 try:
320 lineno = node.lineno - 1
321 end_lineno = node.end_lineno - 1
322 col_offset = node.col_offset
323 end_col_offset = node.end_col_offset
324 except AttributeError:
325 return None
326
327 lines = _splitlines_no_ff(source)
328 if end_lineno == lineno:
329 return lines[lineno].encode()[col_offset:end_col_offset].decode()
330
331 if padded:
332 padding = _pad_whitespace(lines[lineno].encode()[:col_offset].decode())
333 else:
334 padding = ''
335
336 first = padding + lines[lineno].encode()[col_offset:].decode()
337 last = lines[end_lineno].encode()[:end_col_offset].decode()
338 lines = lines[lineno+1:end_lineno]
339
340 lines.insert(0, first)
341 lines.append(last)
342 return ''.join(lines)
343
344
Georg Brandl0c77a822008-06-10 16:37:50 +0000345def walk(node):
346 """
Georg Brandl619e7ba2011-01-09 07:38:51 +0000347 Recursively yield all descendant nodes in the tree starting at *node*
348 (including *node* itself), in no specified order. This is useful if you
349 only want to modify nodes in place and don't care about the context.
Georg Brandl0c77a822008-06-10 16:37:50 +0000350 """
351 from collections import deque
352 todo = deque([node])
353 while todo:
354 node = todo.popleft()
355 todo.extend(iter_child_nodes(node))
356 yield node
357
358
359class NodeVisitor(object):
360 """
361 A node visitor base class that walks the abstract syntax tree and calls a
362 visitor function for every node found. This function may return a value
363 which is forwarded by the `visit` method.
364
365 This class is meant to be subclassed, with the subclass adding visitor
366 methods.
367
368 Per default the visitor functions for the nodes are ``'visit_'`` +
369 class name of the node. So a `TryFinally` node visit function would
370 be `visit_TryFinally`. This behavior can be changed by overriding
371 the `visit` method. If no visitor function exists for a node
372 (return value `None`) the `generic_visit` visitor is used instead.
373
374 Don't use the `NodeVisitor` if you want to apply changes to nodes during
375 traversing. For this a special visitor exists (`NodeTransformer`) that
376 allows modifications.
377 """
378
379 def visit(self, node):
380 """Visit a node."""
381 method = 'visit_' + node.__class__.__name__
382 visitor = getattr(self, method, self.generic_visit)
383 return visitor(node)
384
385 def generic_visit(self, node):
386 """Called if no explicit visitor function exists for a node."""
387 for field, value in iter_fields(node):
388 if isinstance(value, list):
389 for item in value:
390 if isinstance(item, AST):
391 self.visit(item)
392 elif isinstance(value, AST):
393 self.visit(value)
394
Serhiy Storchakac3ea41e2019-08-26 10:13:19 +0300395 def visit_Constant(self, node):
396 value = node.value
397 type_name = _const_node_type_names.get(type(value))
398 if type_name is None:
399 for cls, name in _const_node_type_names.items():
400 if isinstance(value, cls):
401 type_name = name
402 break
403 if type_name is not None:
404 method = 'visit_' + type_name
405 try:
406 visitor = getattr(self, method)
407 except AttributeError:
408 pass
409 else:
410 import warnings
411 warnings.warn(f"{method} is deprecated; add visit_Constant",
412 DeprecationWarning, 2)
413 return visitor(node)
414 return self.generic_visit(node)
415
Georg Brandl0c77a822008-06-10 16:37:50 +0000416
417class NodeTransformer(NodeVisitor):
418 """
419 A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
420 allows modification of nodes.
421
422 The `NodeTransformer` will walk the AST and use the return value of the
423 visitor methods to replace or remove the old node. If the return value of
424 the visitor method is ``None``, the node will be removed from its location,
425 otherwise it is replaced with the return value. The return value may be the
426 original node in which case no replacement takes place.
427
428 Here is an example transformer that rewrites all occurrences of name lookups
429 (``foo``) to ``data['foo']``::
430
431 class RewriteName(NodeTransformer):
432
433 def visit_Name(self, node):
434 return copy_location(Subscript(
435 value=Name(id='data', ctx=Load()),
436 slice=Index(value=Str(s=node.id)),
437 ctx=node.ctx
438 ), node)
439
440 Keep in mind that if the node you're operating on has child nodes you must
441 either transform the child nodes yourself or call the :meth:`generic_visit`
442 method for the node first.
443
444 For nodes that were part of a collection of statements (that applies to all
445 statement nodes), the visitor may also return a list of nodes rather than
446 just a single node.
447
448 Usually you use the transformer like this::
449
450 node = YourTransformer().visit(node)
451 """
452
453 def generic_visit(self, node):
454 for field, old_value in iter_fields(node):
Georg Brandl0c77a822008-06-10 16:37:50 +0000455 if isinstance(old_value, list):
456 new_values = []
457 for value in old_value:
458 if isinstance(value, AST):
459 value = self.visit(value)
460 if value is None:
461 continue
462 elif not isinstance(value, AST):
463 new_values.extend(value)
464 continue
465 new_values.append(value)
466 old_value[:] = new_values
467 elif isinstance(old_value, AST):
468 new_node = self.visit(old_value)
469 if new_node is None:
470 delattr(node, field)
471 else:
472 setattr(node, field, new_node)
473 return node
Serhiy Storchaka3f228112018-09-27 17:42:37 +0300474
475
476# The following code is for backward compatibility.
477# It will be removed in future.
478
479def _getter(self):
480 return self.value
481
482def _setter(self, value):
483 self.value = value
484
485Constant.n = property(_getter, _setter)
486Constant.s = property(_getter, _setter)
487
488class _ABC(type):
489
490 def __instancecheck__(cls, inst):
491 if not isinstance(inst, Constant):
492 return False
493 if cls in _const_types:
494 try:
495 value = inst.value
496 except AttributeError:
497 return False
498 else:
Anthony Sottile74176222019-01-18 11:30:28 -0800499 return (
500 isinstance(value, _const_types[cls]) and
501 not isinstance(value, _const_types_not.get(cls, ()))
502 )
Serhiy Storchaka3f228112018-09-27 17:42:37 +0300503 return type.__instancecheck__(cls, inst)
504
505def _new(cls, *args, **kwargs):
506 if cls in _const_types:
507 return Constant(*args, **kwargs)
508 return Constant.__new__(cls, *args, **kwargs)
509
510class Num(Constant, metaclass=_ABC):
511 _fields = ('n',)
512 __new__ = _new
513
514class Str(Constant, metaclass=_ABC):
515 _fields = ('s',)
516 __new__ = _new
517
518class Bytes(Constant, metaclass=_ABC):
519 _fields = ('s',)
520 __new__ = _new
521
522class NameConstant(Constant, metaclass=_ABC):
523 __new__ = _new
524
525class Ellipsis(Constant, metaclass=_ABC):
526 _fields = ()
527
528 def __new__(cls, *args, **kwargs):
529 if cls is Ellipsis:
530 return Constant(..., *args, **kwargs)
531 return Constant.__new__(cls, *args, **kwargs)
532
533_const_types = {
534 Num: (int, float, complex),
535 Str: (str,),
536 Bytes: (bytes,),
537 NameConstant: (type(None), bool),
538 Ellipsis: (type(...),),
539}
Anthony Sottile74176222019-01-18 11:30:28 -0800540_const_types_not = {
541 Num: (bool,),
542}
Serhiy Storchakac3ea41e2019-08-26 10:13:19 +0300543_const_node_type_names = {
544 bool: 'NameConstant', # should be before int
545 type(None): 'NameConstant',
546 int: 'Num',
547 float: 'Num',
548 complex: 'Num',
549 str: 'Str',
550 bytes: 'Bytes',
551 type(...): 'Ellipsis',
552}
Serhiy Storchaka832e8642019-09-09 23:36:13 +0300553
554
555def main():
556 import argparse
557
558 parser = argparse.ArgumentParser(prog='python -m ast')
559 parser.add_argument('infile', type=argparse.FileType(mode='rb'), nargs='?',
560 default='-',
561 help='the file to parse; defaults to stdin')
562 parser.add_argument('-m', '--mode', default='exec',
563 choices=('exec', 'single', 'eval', 'func_type'),
564 help='specify what kind of code must be parsed')
565 parser.add_argument('-a', '--include-attributes', action='store_true',
566 help='include attributes such as line numbers and '
567 'column offsets')
568 args = parser.parse_args()
569
570 with args.infile as infile:
571 source = infile.read()
572 tree = parse(source, args.infile.name, args.mode, type_comments=True)
573 print(dump(tree, include_attributes=args.include_attributes, indent=3))
574
575if __name__ == '__main__':
576 main()