From 217c2b1fe09be28592b3650236526f301362088f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Feb 2024 11:52:07 +0300 Subject: [PATCH 01/10] gh-116023: Add `show_empty=True` to `ast.dump` --- Doc/library/ast.rst | 8 +++- Lib/ast.py | 11 ++++- Lib/test/test_ast.py | 45 +++++++++++++++++++ ...-02-28-11-51-51.gh-issue-116023.CGYhFh.rst | 2 + 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index d629393f023c8a..a93578726b60ac 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2422,7 +2422,7 @@ and classes for traversing abstract syntax trees: node = YourTransformer().visit(node) -.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None) +.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=True) Return a formatted dump of the tree in *node*. This is mainly useful for debugging purposes. If *annotate_fields* is true (by default), @@ -2439,9 +2439,15 @@ and classes for traversing abstract syntax trees: indents that many spaces per level. If *indent* is a string (such as ``"\t"``), that string is used to indent each level. + If *show_empty* is ``False``, then empty lists, fields that are ``None``, + and empty strings will be ommitted from the output for better readability. + .. versionchanged:: 3.9 Added the *indent* option. + .. versionchanged:: 3.13 + Added the *show_empty* option. + .. _ast-compiler-flags: diff --git a/Lib/ast.py b/Lib/ast.py index b8c4ce6f919e6b..f80061f58d6647 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -114,7 +114,11 @@ def _convert(node): return _convert(node_or_string) -def dump(node, annotate_fields=True, include_attributes=False, *, indent=None): +def dump( + node, annotate_fields=True, include_attributes=False, + *, + indent=None, show_empty=True, +): """ Return a formatted dump of the tree in node. This is mainly useful for debugging purposes. If annotate_fields is true (by default), @@ -125,6 +129,8 @@ def dump(node, annotate_fields=True, include_attributes=False, *, indent=None): include_attributes can be set to true. If indent is a non-negative integer or string, then the tree will be pretty-printed with that indent level. None (the default) selects the single line representation. + If show_empty is False, then empty lists, fields that are None, + and empty strings will be ommitted from the output for better readability. """ def _format(node, level=0): if indent is not None: @@ -149,6 +155,8 @@ def _format(node, level=0): keywords = True continue value, simple = _format(value, level) + if not show_empty and value in empty_values: + continue allsimple = allsimple and simple if keywords: args.append('%s=%s' % (name, value)) @@ -178,6 +186,7 @@ def _format(node, level=0): raise TypeError('expected AST, got %r' % node.__class__.__name__) if indent is not None and not isinstance(indent, str): indent = ' ' * indent + empty_values = {None, "''", "[]"} return _format(node)[0] diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 7cecf319e3638f..f76f49acf3bd3c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1334,6 +1334,51 @@ def test_dump_incomplete(self): "Raise(cause=Name('e', Load()))" ) + def test_dump_show_empty_false(self): + self.assertEqual( + ast.dump(ast.arguments(), show_empty=False), + "arguments()", + ) + + self.assertEqual( + # Corner case: there are no real `Name` instances with `id=''`: + ast.dump(ast.Name(id='', ctx=ast.Load()), show_empty=False), + "Name(ctx=Load())", + ) + + def check_text(code, expected, **kwargs): + nodes = ast.parse(code) + self.assertEqual( + ast.dump(nodes, show_empty=False, **kwargs), + expected, + ) + + check_text( + "def a(b: int = 0, *, c): ...", + "Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))])])", + ) + + check_text( + "def a(b: int = 0, *, c): ...", + "Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)])", + include_attributes=True, + ) + + check_text( + 'spam(eggs, "and cheese")', + "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])", + ) + + check_text( + 'spam(eggs, text="and cheese")', + "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))])", + ) + + check_text( + "import _ast as ast; from module import sub", + "Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)])", + ) + def test_copy_location(self): src = ast.parse('1 + 1', mode='eval') src.body.right = ast.copy_location(ast.Constant(2), src.body.right) diff --git a/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst new file mode 100644 index 00000000000000..69b361a5685171 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst @@ -0,0 +1,2 @@ +Add ``show_empty=True`` parameter to :func:`ast.dump` to optionally disable +showing empty fields in the AST. From 41b5baaf707c8686ad3a9e7a76f4fb6f4fb1a2fe Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Feb 2024 15:52:46 +0300 Subject: [PATCH 02/10] Address review --- Doc/library/ast.rst | 2 +- Lib/ast.py | 11 ++++++++--- Lib/test/test_ast.py | 12 +++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index a93578726b60ac..8c0fd2afc31acc 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2440,7 +2440,7 @@ and classes for traversing abstract syntax trees: that string is used to indent each level. If *show_empty* is ``False``, then empty lists, fields that are ``None``, - and empty strings will be ommitted from the output for better readability. + and empty strings will be omitted from the output for better readability. .. versionchanged:: 3.9 Added the *indent* option. diff --git a/Lib/ast.py b/Lib/ast.py index f80061f58d6647..deb19615ff128c 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -154,9 +154,14 @@ def _format(node, level=0): if value is None and getattr(cls, name, ...) is None: keywords = True continue - value, simple = _format(value, level) - if not show_empty and value in empty_values: + if ( + not show_empty + and value in empty_values + and not isinstance(node, Constant) + ): + # Special case: `Constant(value=None)` continue + value, simple = _format(value, level) allsimple = allsimple and simple if keywords: args.append('%s=%s' % (name, value)) @@ -186,7 +191,7 @@ def _format(node, level=0): raise TypeError('expected AST, got %r' % node.__class__.__name__) if indent is not None and not isinstance(indent, str): indent = ' ' * indent - empty_values = {None, "''", "[]"} + empty_values = (None, []) return _format(node)[0] diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index f76f49acf3bd3c..cad76b8e528f9d 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1343,7 +1343,17 @@ def test_dump_show_empty_false(self): self.assertEqual( # Corner case: there are no real `Name` instances with `id=''`: ast.dump(ast.Name(id='', ctx=ast.Load()), show_empty=False), - "Name(ctx=Load())", + "Name(id='', ctx=Load())", + ) + + self.assertEqual( + ast.dump(ast.Constant(value=None), show_empty=False), + 'Constant(value=None)', + ) + + self.assertEqual( + ast.dump(ast.Constant(value=''), show_empty=False), + "Constant(value='')", ) def check_text(code, expected, **kwargs): From 4326fe8deb75840af6343b96b67c538c088e1982 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 15 Mar 2024 14:50:17 +0300 Subject: [PATCH 03/10] Address review --- Doc/library/ast.rst | 4 ++-- Lib/ast.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 8c0fd2afc31acc..c63bd8c8d0a345 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2439,8 +2439,8 @@ and classes for traversing abstract syntax trees: indents that many spaces per level. If *indent* is a string (such as ``"\t"``), that string is used to indent each level. - If *show_empty* is ``False``, then empty lists, fields that are ``None``, - and empty strings will be omitted from the output for better readability. + If *show_empty* is ``False``, then empty lists and fields that are ``None`` + will be omitted from the output for better readability. .. versionchanged:: 3.9 Added the *indent* option. diff --git a/Lib/ast.py b/Lib/ast.py index deb19615ff128c..f1e0cc2f7dc2fc 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -129,8 +129,8 @@ def dump( include_attributes can be set to true. If indent is a non-negative integer or string, then the tree will be pretty-printed with that indent level. None (the default) selects the single line representation. - If show_empty is False, then empty lists, fields that are None, - and empty strings will be ommitted from the output for better readability. + If show_empty is False, then empty lists and fields that are None + will be ommitted from the output for better readability. """ def _format(node, level=0): if indent is not None: @@ -156,7 +156,7 @@ def _format(node, level=0): continue if ( not show_empty - and value in empty_values + and (value is None or value == []) and not isinstance(node, Constant) ): # Special case: `Constant(value=None)` @@ -191,7 +191,6 @@ def _format(node, level=0): raise TypeError('expected AST, got %r' % node.__class__.__name__) if indent is not None and not isinstance(indent, str): indent = ' ' * indent - empty_values = (None, []) return _format(node)[0] From 56459dec9c81845cefb0b373c5a97d7beb7ff915 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 22 Mar 2024 13:27:11 +0300 Subject: [PATCH 04/10] Use `show_empty=False` by default --- Doc/library/ast.rst | 2 +- Lib/ast.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index c63bd8c8d0a345..ae12887f5b2fb8 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2422,7 +2422,7 @@ and classes for traversing abstract syntax trees: node = YourTransformer().visit(node) -.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=True) +.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=False) Return a formatted dump of the tree in *node*. This is mainly useful for debugging purposes. If *annotate_fields* is true (by default), diff --git a/Lib/ast.py b/Lib/ast.py index f1e0cc2f7dc2fc..51abdf9b4ccabe 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -117,7 +117,7 @@ def _convert(node): def dump( node, annotate_fields=True, include_attributes=False, *, - indent=None, show_empty=True, + indent=None, show_empty=False, ): """ Return a formatted dump of the tree in node. This is mainly useful for From dd98ca48315e80b86a6016246b0bdd283237e557 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 22 Mar 2024 13:44:46 +0300 Subject: [PATCH 05/10] Address review, hide empty values by default --- Lib/test/test_ast.py | 95 ++++++++++--------- ...-02-28-11-51-51.gh-issue-116023.CGYhFh.rst | 2 +- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index cad76b8e528f9d..eb436881757f12 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1227,21 +1227,20 @@ def test_dump(self): node = ast.parse('spam(eggs, "and cheese")') self.assertEqual(ast.dump(node), "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), " - "args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')], " - "keywords=[]))], type_ignores=[])" + "args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])" ) self.assertEqual(ast.dump(node, annotate_fields=False), "Module([Expr(Call(Name('spam', Load()), [Name('eggs', Load()), " - "Constant('and cheese')], []))], [])" + "Constant('and cheese')]))])" ) self.assertEqual(ast.dump(node, include_attributes=True), "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load(), " "lineno=1, col_offset=0, end_lineno=1, end_col_offset=4), " "args=[Name(id='eggs', ctx=Load(), lineno=1, col_offset=5, " "end_lineno=1, end_col_offset=9), Constant(value='and cheese', " - "lineno=1, col_offset=11, end_lineno=1, end_col_offset=23)], keywords=[], " + "lineno=1, col_offset=11, end_lineno=1, end_col_offset=23)], " "lineno=1, col_offset=0, end_lineno=1, end_col_offset=24), " - "lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)], type_ignores=[])" + "lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)])" ) def test_dump_indent(self): @@ -1254,9 +1253,7 @@ def test_dump_indent(self): func=Name(id='spam', ctx=Load()), args=[ Name(id='eggs', ctx=Load()), - Constant(value='and cheese')], - keywords=[]))], - type_ignores=[])""") + Constant(value='and cheese')]))])""") self.assertEqual(ast.dump(node, annotate_fields=False, indent='\t'), """\ Module( \t[ @@ -1265,9 +1262,7 @@ def test_dump_indent(self): \t\t\t\tName('spam', Load()), \t\t\t\t[ \t\t\t\t\tName('eggs', Load()), -\t\t\t\t\tConstant('and cheese')], -\t\t\t\t[]))], -\t[])""") +\t\t\t\t\tConstant('and cheese')]))])""") self.assertEqual(ast.dump(node, include_attributes=True, indent=3), """\ Module( body=[ @@ -1294,7 +1289,6 @@ def test_dump_indent(self): col_offset=11, end_lineno=1, end_col_offset=23)], - keywords=[], lineno=1, col_offset=0, end_lineno=1, @@ -1302,8 +1296,7 @@ def test_dump_indent(self): lineno=1, col_offset=0, end_lineno=1, - end_col_offset=24)], - type_ignores=[])""") + end_col_offset=24)])""") def test_dump_incomplete(self): node = ast.Raise(lineno=3, col_offset=4) @@ -1334,59 +1327,76 @@ def test_dump_incomplete(self): "Raise(cause=Name('e', Load()))" ) - def test_dump_show_empty_false(self): - self.assertEqual( - ast.dump(ast.arguments(), show_empty=False), - "arguments()", + def test_dump_show_empty(self): + def check_node(node, empty, full, **kwargs): + with self.subTest(show_empty=False): + self.assertEqual( + ast.dump(node, show_empty=False, **kwargs), + empty, + ) + with self.subTest(show_empty=True): + self.assertEqual( + ast.dump(node, show_empty=True, **kwargs), + full, + ) + + def check_text(code, empty, full, **kwargs): + check_node(ast.parse(code), empty, full, **kwargs) + + check_node( + ast.arguments(), + empty="arguments()", + full="arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[])", ) - self.assertEqual( + check_node( # Corner case: there are no real `Name` instances with `id=''`: - ast.dump(ast.Name(id='', ctx=ast.Load()), show_empty=False), - "Name(id='', ctx=Load())", + ast.Name(id='', ctx=ast.Load()), + empty="Name(id='', ctx=Load())", + full="Name(id='', ctx=Load())", ) - self.assertEqual( - ast.dump(ast.Constant(value=None), show_empty=False), - 'Constant(value=None)', + check_node( + ast.Constant(value=None), + empty="Constant(value=None)", + full="Constant(value=None)", ) - self.assertEqual( - ast.dump(ast.Constant(value=''), show_empty=False), - "Constant(value='')", + check_node( + ast.Constant(value=''), + empty="Constant(value='')", + full="Constant(value='')", ) - def check_text(code, expected, **kwargs): - nodes = ast.parse(code) - self.assertEqual( - ast.dump(nodes, show_empty=False, **kwargs), - expected, - ) - check_text( "def a(b: int = 0, *, c): ...", - "Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))])])", + empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))])])", + full="Module(body=[FunctionDef(name='a', args=arguments(posonlyargs=[], args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))], decorator_list=[], type_params=[])], type_ignores=[])", ) check_text( "def a(b: int = 0, *, c): ...", - "Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)])", + empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)])", + full="Module(body=[FunctionDef(name='a', args=arguments(posonlyargs=[], args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], decorator_list=[], type_params=[], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)], type_ignores=[])", include_attributes=True, ) check_text( 'spam(eggs, "and cheese")', - "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])", + empty="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])", + full="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')], keywords=[]))], type_ignores=[])", ) check_text( 'spam(eggs, text="and cheese")', - "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))])", + empty="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))])", + full="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))], type_ignores=[])", ) check_text( "import _ast as ast; from module import sub", - "Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)])", + empty="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)])", + full="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)], type_ignores=[])", ) def test_copy_location(self): @@ -1416,14 +1426,13 @@ def test_fix_missing_locations(self): "Module(body=[Expr(value=Call(func=Name(id='write', ctx=Load(), " "lineno=1, col_offset=0, end_lineno=1, end_col_offset=5), " "args=[Constant(value='spam', lineno=1, col_offset=6, end_lineno=1, " - "end_col_offset=12)], keywords=[], lineno=1, col_offset=0, end_lineno=1, " + "end_col_offset=12)], lineno=1, col_offset=0, end_lineno=1, " "end_col_offset=13), lineno=1, col_offset=0, end_lineno=1, " "end_col_offset=13), Expr(value=Call(func=Name(id='spam', ctx=Load(), " "lineno=1, col_offset=0, end_lineno=1, end_col_offset=0), " "args=[Constant(value='eggs', lineno=1, col_offset=0, end_lineno=1, " - "end_col_offset=0)], keywords=[], lineno=1, col_offset=0, end_lineno=1, " - "end_col_offset=0), lineno=1, col_offset=0, end_lineno=1, end_col_offset=0)], " - "type_ignores=[])" + "end_col_offset=0)], lineno=1, col_offset=0, end_lineno=1, " + "end_col_offset=0), lineno=1, col_offset=0, end_lineno=1, end_col_offset=0)])" ) def test_increment_lineno(self): diff --git a/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst index 69b361a5685171..5ee0db491700a9 100644 --- a/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst +++ b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst @@ -1,2 +1,2 @@ -Add ``show_empty=True`` parameter to :func:`ast.dump` to optionally disable +Add ``show_empty=False`` parameter to :func:`ast.dump` to optionally enable showing empty fields in the AST. From 2c70b8365516066c98bd304c0a1fc8e8e108ee13 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 22 Mar 2024 15:05:07 +0300 Subject: [PATCH 06/10] Try to solve doctest failure --- Doc/library/ast.rst | 195 +++++++++++++++----------------------------- 1 file changed, 66 insertions(+), 129 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 9547dcf7a0e80a..b9bcc9bb83dd02 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -173,8 +173,7 @@ Root nodes Assign( targets=[ Name(id='x', ctx=Store())], - value=Constant(value=1))], - type_ignores=[]) + value=Constant(value=1))]) .. class:: Expression(body) @@ -302,8 +301,7 @@ Literals value=Call( func=Name(id='sin', ctx=Load()), args=[ - Name(id='a', ctx=Load())], - keywords=[]), + Name(id='a', ctx=Load())]), conversion=-1, format_spec=JoinedStr( values=[ @@ -398,8 +396,7 @@ Variables Module( body=[ Expr( - value=Name(id='a', ctx=Load()))], - type_ignores=[]) + value=Name(id='a', ctx=Load()))]) >>> print(ast.dump(ast.parse('a = 1'), indent=4)) Module( @@ -407,16 +404,14 @@ Variables Assign( targets=[ Name(id='a', ctx=Store())], - value=Constant(value=1))], - type_ignores=[]) + value=Constant(value=1))]) >>> print(ast.dump(ast.parse('del a'), indent=4)) Module( body=[ Delete( targets=[ - Name(id='a', ctx=Del())])], - type_ignores=[]) + Name(id='a', ctx=Del())])]) .. class:: Starred(value, ctx) @@ -439,8 +434,7 @@ Variables value=Name(id='b', ctx=Store()), ctx=Store())], ctx=Store())], - value=Name(id='it', ctx=Load()))], - type_ignores=[]) + value=Name(id='it', ctx=Load()))]) .. _ast-expressions: @@ -463,8 +457,7 @@ Expressions Expr( value=UnaryOp( op=USub(), - operand=Name(id='a', ctx=Load())))], - type_ignores=[]) + operand=Name(id='a', ctx=Load())))]) .. class:: UnaryOp(op, operand) @@ -729,7 +722,10 @@ Comprehensions .. doctest:: - >>> print(ast.dump(ast.parse('[x for x in numbers]', mode='eval'), indent=4)) + >>> print(ast.dump( + ... ast.parse('[x for x in numbers]', mode='eval'), + ... indent=4, show_empty=True, + ... )) Expression( body=ListComp( elt=Name(id='x', ctx=Load()), @@ -739,7 +735,10 @@ Comprehensions iter=Name(id='numbers', ctx=Load()), ifs=[], is_async=0)])) - >>> print(ast.dump(ast.parse('{x: x**2 for x in numbers}', mode='eval'), indent=4)) + >>> print(ast.dump( + ... ast.parse('{x: x**2 for x in numbers}', mode='eval'), + ... indent=4, show_empty=True, + ... )) Expression( body=DictComp( key=Name(id='x', ctx=Load()), @@ -753,7 +752,10 @@ Comprehensions iter=Name(id='numbers', ctx=Load()), ifs=[], is_async=0)])) - >>> print(ast.dump(ast.parse('{x for x in numbers}', mode='eval'), indent=4)) + >>> print(ast.dump( + ... ast.parse('{x for x in numbers}', mode='eval'), + ... indent=4, show_empty=True, + ... )) Expression( body=SetComp( elt=Name(id='x', ctx=Load()), @@ -784,18 +786,15 @@ Comprehensions elt=Call( func=Name(id='ord', ctx=Load()), args=[ - Name(id='c', ctx=Load())], - keywords=[]), + Name(id='c', ctx=Load())]), generators=[ comprehension( target=Name(id='line', ctx=Store()), iter=Name(id='file', ctx=Load()), - ifs=[], is_async=0), comprehension( target=Name(id='c', ctx=Store()), iter=Name(id='line', ctx=Load()), - ifs=[], is_async=0)])) >>> print(ast.dump(ast.parse('(n**2 for n in it if n>5 if n<10)', mode='eval'), @@ -834,7 +833,6 @@ Comprehensions comprehension( target=Name(id='i', ctx=Store()), iter=Name(id='soc', ctx=Load()), - ifs=[], is_async=1)])) @@ -864,8 +862,7 @@ Statements targets=[ Name(id='a', ctx=Store()), Name(id='b', ctx=Store())], - value=Constant(value=1))], - type_ignores=[]) + value=Constant(value=1))]) >>> print(ast.dump(ast.parse('a,b = c'), indent=4)) # Unpacking Module( @@ -877,8 +874,7 @@ Statements Name(id='a', ctx=Store()), Name(id='b', ctx=Store())], ctx=Store())], - value=Name(id='c', ctx=Load()))], - type_ignores=[]) + value=Name(id='c', ctx=Load()))]) .. class:: AnnAssign(target, annotation, value, simple) @@ -898,8 +894,7 @@ Statements AnnAssign( target=Name(id='c', ctx=Store()), annotation=Name(id='int', ctx=Load()), - simple=1)], - type_ignores=[]) + simple=1)]) >>> print(ast.dump(ast.parse('(a): int = 1'), indent=4)) # Annotation with parenthesis Module( @@ -908,8 +903,7 @@ Statements target=Name(id='a', ctx=Store()), annotation=Name(id='int', ctx=Load()), value=Constant(value=1), - simple=0)], - type_ignores=[]) + simple=0)]) >>> print(ast.dump(ast.parse('a.b: int'), indent=4)) # Attribute annotation Module( @@ -920,8 +914,7 @@ Statements attr='b', ctx=Store()), annotation=Name(id='int', ctx=Load()), - simple=0)], - type_ignores=[]) + simple=0)]) >>> print(ast.dump(ast.parse('a[1]: int'), indent=4)) # Subscript annotation Module( @@ -932,8 +925,7 @@ Statements slice=Constant(value=1), ctx=Store()), annotation=Name(id='int', ctx=Load()), - simple=0)], - type_ignores=[]) + simple=0)]) .. class:: AugAssign(target, op, value) @@ -954,8 +946,7 @@ Statements AugAssign( target=Name(id='x', ctx=Store()), op=Add(), - value=Constant(value=2))], - type_ignores=[]) + value=Constant(value=2))]) .. class:: Raise(exc, cause) @@ -971,8 +962,7 @@ Statements body=[ Raise( exc=Name(id='x', ctx=Load()), - cause=Name(id='y', ctx=Load()))], - type_ignores=[]) + cause=Name(id='y', ctx=Load()))]) .. class:: Assert(test, msg) @@ -987,8 +977,7 @@ Statements body=[ Assert( test=Name(id='x', ctx=Load()), - msg=Name(id='y', ctx=Load()))], - type_ignores=[]) + msg=Name(id='y', ctx=Load()))]) .. class:: Delete(targets) @@ -1005,8 +994,7 @@ Statements targets=[ Name(id='x', ctx=Del()), Name(id='y', ctx=Del()), - Name(id='z', ctx=Del())])], - type_ignores=[]) + Name(id='z', ctx=Del())])]) .. class:: Pass() @@ -1018,8 +1006,7 @@ Statements >>> print(ast.dump(ast.parse('pass'), indent=4)) Module( body=[ - Pass()], - type_ignores=[]) + Pass()]) .. class:: TypeAlias(name, type_params, value) @@ -1036,9 +1023,7 @@ Statements body=[ TypeAlias( name=Name(id='Alias', ctx=Store()), - type_params=[], - value=Name(id='int', ctx=Load()))], - type_ignores=[]) + value=Name(id='int', ctx=Load()))]) .. versionadded:: 3.12 @@ -1061,8 +1046,7 @@ Imports names=[ alias(name='x'), alias(name='y'), - alias(name='z')])], - type_ignores=[]) + alias(name='z')])]) .. class:: ImportFrom(module, names, level) @@ -1083,8 +1067,7 @@ Imports alias(name='x'), alias(name='y'), alias(name='z')], - level=0)], - type_ignores=[]) + level=0)]) .. class:: alias(name, asname) @@ -1102,8 +1085,7 @@ Imports names=[ alias(name='a', asname='b'), alias(name='c')], - level=2)], - type_ignores=[]) + level=2)]) Control flow ^^^^^^^^^^^^ @@ -1146,8 +1128,7 @@ Control flow value=Constant(value=Ellipsis))], orelse=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. class:: For(target, iter, body, orelse, type_comment) @@ -1181,8 +1162,7 @@ Control flow value=Constant(value=Ellipsis))], orelse=[ Expr( - value=Constant(value=Ellipsis))])], - type_ignores=[]) + value=Constant(value=Ellipsis))])]) .. class:: While(test, body, orelse) @@ -1207,8 +1187,7 @@ Control flow value=Constant(value=Ellipsis))], orelse=[ Expr( - value=Constant(value=Ellipsis))])], - type_ignores=[]) + value=Constant(value=Ellipsis))])]) .. class:: Break @@ -1242,9 +1221,7 @@ Control flow body=[ Break()], orelse=[ - Continue()])], - orelse=[])], - type_ignores=[]) + Continue()])])]) .. class:: Try(body, handlers, orelse, finalbody) @@ -1289,8 +1266,7 @@ Control flow value=Constant(value=Ellipsis))], finalbody=[ Expr( - value=Constant(value=Ellipsis))])], - type_ignores=[]) + value=Constant(value=Ellipsis))])]) .. class:: TryStar(body, handlers, orelse, finalbody) @@ -1318,10 +1294,7 @@ Control flow type=Name(id='Exception', ctx=Load()), body=[ Expr( - value=Constant(value=Ellipsis))])], - orelse=[], - finalbody=[])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.11 @@ -1353,10 +1326,7 @@ Control flow ExceptHandler( type=Name(id='TypeError', ctx=Load()), body=[ - Pass()])], - orelse=[], - finalbody=[])], - type_ignores=[]) + Pass()])])]) .. class:: With(items, body, type_comment) @@ -1398,9 +1368,7 @@ Control flow func=Name(id='something', ctx=Load()), args=[ Name(id='b', ctx=Load()), - Name(id='d', ctx=Load())], - keywords=[]))])], - type_ignores=[]) + Name(id='d', ctx=Load())]))])]) Pattern matching @@ -1436,7 +1404,7 @@ Pattern matching ... ... ... case tuple(): ... ... - ... """), indent=4)) + ... """), indent=4, show_empty=True)) Module( body=[ Match( @@ -1492,8 +1460,7 @@ Pattern matching value=Constant(value='Relevant')), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1519,8 +1486,7 @@ Pattern matching pattern=MatchSingleton(value=None), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1552,8 +1518,7 @@ Pattern matching value=Constant(value=2))]), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1594,8 +1559,7 @@ Pattern matching MatchStar()]), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1621,7 +1585,7 @@ Pattern matching ... ... ... case {**rest}: ... ... - ... """), indent=4)) + ... """), indent=4, show_empty=True)) Module( body=[ Match( @@ -1672,7 +1636,7 @@ Pattern matching ... ... ... case Point3D(x=0, y=0, z=0): ... ... - ... """), indent=4)) + ... """), indent=4, show_empty=True)) Module( body=[ Match( @@ -1685,9 +1649,7 @@ Pattern matching MatchValue( value=Constant(value=0)), MatchValue( - value=Constant(value=0))], - kwd_attrs=[], - kwd_patterns=[]), + value=Constant(value=0))]), body=[ Expr( value=Constant(value=Ellipsis))]), @@ -1751,8 +1713,7 @@ Pattern matching pattern=MatchAs(), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1785,8 +1746,7 @@ Pattern matching MatchAs(name='y')]), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1818,8 +1778,7 @@ aliases. value=Subscript( value=Name(id='list', ctx=Load()), slice=Name(id='T', ctx=Load()), - ctx=Load()))], - type_ignores=[]) + ctx=Load()))]) .. versionadded:: 3.12 @@ -1843,8 +1802,7 @@ aliases. Name(id='P', ctx=Load()), Name(id='int', ctx=Load())], ctx=Load()), - ctx=Load()))], - type_ignores=[]) + ctx=Load()))]) .. versionadded:: 3.12 @@ -1869,8 +1827,7 @@ aliases. value=Name(id='Ts', ctx=Load()), ctx=Load())], ctx=Load()), - ctx=Load()))], - type_ignores=[]) + ctx=Load()))]) .. versionadded:: 3.12 @@ -1904,7 +1861,7 @@ Function and class definitions .. doctest:: - >>> print(ast.dump(ast.parse('lambda x,y: ...'), indent=4)) + >>> print(ast.dump(ast.parse('lambda x,y: ...'), indent=4, show_empty=True)) Module( body=[ Expr( @@ -1957,7 +1914,6 @@ Function and class definitions FunctionDef( name='f', args=arguments( - posonlyargs=[], args=[ arg( arg='a', @@ -1980,9 +1936,7 @@ Function and class definitions decorator_list=[ Name(id='decorator1', ctx=Load()), Name(id='decorator2', ctx=Load())], - returns=Constant(value='return annotation'), - type_params=[])], - type_ignores=[]) + returns=Constant(value='return annotation'))]) .. class:: Return(value) @@ -1995,8 +1949,7 @@ Function and class definitions Module( body=[ Return( - value=Constant(value=4))], - type_ignores=[]) + value=Constant(value=4))]) .. class:: Yield(value) @@ -2012,16 +1965,14 @@ Function and class definitions body=[ Expr( value=Yield( - value=Name(id='x', ctx=Load())))], - type_ignores=[]) + value=Name(id='x', ctx=Load())))]) >>> print(ast.dump(ast.parse('yield from x'), indent=4)) Module( body=[ Expr( value=YieldFrom( - value=Name(id='x', ctx=Load())))], - type_ignores=[]) + value=Name(id='x', ctx=Load())))]) .. class:: Global(names) @@ -2038,8 +1989,7 @@ Function and class definitions names=[ 'x', 'y', - 'z'])], - type_ignores=[]) + 'z'])]) >>> print(ast.dump(ast.parse('nonlocal x,y,z'), indent=4)) Module( @@ -2048,8 +1998,7 @@ Function and class definitions names=[ 'x', 'y', - 'z'])], - type_ignores=[]) + 'z'])]) .. class:: ClassDef(name, bases, keywords, body, decorator_list, type_params) @@ -2089,9 +2038,7 @@ Function and class definitions Pass()], decorator_list=[ Name(id='decorator1', ctx=Load()), - Name(id='decorator2', ctx=Load())], - type_params=[])], - type_ignores=[]) + Name(id='decorator2', ctx=Load())])]) .. versionchanged:: 3.12 Added ``type_params``. @@ -2123,22 +2070,12 @@ Async and await body=[ AsyncFunctionDef( name='f', - args=arguments( - posonlyargs=[], - args=[], - kwonlyargs=[], - kw_defaults=[], - defaults=[]), + args=arguments(), body=[ Expr( value=Await( value=Call( - func=Name(id='other_func', ctx=Load()), - args=[], - keywords=[])))], - decorator_list=[], - type_params=[])], - type_ignores=[]) + func=Name(id='other_func', ctx=Load()))))])]) .. class:: AsyncFor(target, iter, body, orelse, type_comment) From fab07758c30025cdc93a0add589e977adc5f713e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 22 Mar 2024 16:37:16 +0300 Subject: [PATCH 07/10] Add `MatchSingletone(value=None)` as a special case --- Doc/library/ast.rst | 4 +++- Lib/ast.py | 5 +++-- Lib/test/test_ast.py | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index b9bcc9bb83dd02..5b3f0a0b20dafe 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -1649,7 +1649,9 @@ Pattern matching MatchValue( value=Constant(value=0)), MatchValue( - value=Constant(value=0))]), + value=Constant(value=0))], + kwd_attrs=[], + kwd_patterns=[]), body=[ Expr( value=Constant(value=Ellipsis))]), diff --git a/Lib/ast.py b/Lib/ast.py index 51abdf9b4ccabe..c5d79994790371 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -157,9 +157,10 @@ def _format(node, level=0): if ( not show_empty and (value is None or value == []) - and not isinstance(node, Constant) + # Special cases: + # `Constant(value=None)` and `MatchSingleton(value=None)` + and not isinstance(node, (Constant, MatchSingleton)) ): - # Special case: `Constant(value=None)` continue value, simple = _format(value, level) allsimple = allsimple and simple diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index eb436881757f12..45e3e6f7128f4c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1356,6 +1356,12 @@ def check_text(code, empty, full, **kwargs): full="Name(id='', ctx=Load())", ) + check_node( + ast.MatchSingleton(value=None), + empty="MatchSingleton(value=None)", + full="MatchSingleton(value=None)", + ) + check_node( ast.Constant(value=None), empty="Constant(value=None)", From cf637e2744f6ef2de5b65dabca10ee789275bad7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 16 Apr 2024 14:31:47 +0300 Subject: [PATCH 08/10] Address review --- Lib/ast.py | 5 +++++ Lib/test/test_ast.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Lib/ast.py b/Lib/ast.py index c5d79994790371..a56aa0572b0095 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -143,6 +143,7 @@ def _format(node, level=0): if isinstance(node, AST): cls = type(node) args = [] + args_buffer = [] allsimple = True keywords = annotate_fields for name in node._fields: @@ -161,7 +162,11 @@ def _format(node, level=0): # `Constant(value=None)` and `MatchSingleton(value=None)` and not isinstance(node, (Constant, MatchSingleton)) ): + args_buffer.append(repr(value)) continue + elif not keywords: + args.extend(args_buffer) + args_buffer = [] value, simple = _format(value, level) allsimple = allsimple and simple if keywords: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 6964e2b5691d31..44bcb9bae1cfde 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1326,6 +1326,41 @@ def test_dump_incomplete(self): self.assertEqual(ast.dump(node, annotate_fields=False), "Raise(cause=Name('e', Load()))" ) + # Arguments: + node = ast.arguments(args=[ast.arg("x")]) + self.assertEqual(ast.dump(node, annotate_fields=False), + "arguments([], [arg('x')])", + ) + node = ast.arguments(posonlyargs=[ast.arg("x")]) + self.assertEqual(ast.dump(node, annotate_fields=False), + "arguments([arg('x')])", + ) + node = ast.arguments(posonlyargs=[ast.arg("x")], kwonlyargs=[ast.arg('y')]) + self.assertEqual(ast.dump(node, annotate_fields=False), + "arguments([arg('x')], kwonlyargs=[arg('y')])", + ) + node = ast.arguments(args=[ast.arg("x")], kwonlyargs=[ast.arg('y')]) + self.assertEqual(ast.dump(node, annotate_fields=False), + "arguments([], [arg('x')], kwonlyargs=[arg('y')])", + ) + node = ast.arguments() + self.assertEqual(ast.dump(node, annotate_fields=False), + "arguments()", + ) + # Classes: + node = ast.ClassDef( + 'T', + [], + [ast.keyword('a', ast.Constant(None))], + [], + [ast.Name('dataclass')], + ) + self.assertEqual(ast.dump(node), + "ClassDef(name='T', keywords=[keyword(arg='a', value=Constant(value=None))], decorator_list=[Name(id='dataclass')])", + ) + self.assertEqual(ast.dump(node, annotate_fields=False), + "ClassDef('T', [], [keyword('a', Constant(None))], [], [Name('dataclass')])", + ) def test_dump_show_empty(self): def check_node(node, empty, full, **kwargs): From 15aa403ca1b55a30371e514f50a2d14f3840195e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 18 Apr 2024 14:38:10 +0300 Subject: [PATCH 09/10] Address review --- Doc/library/ast.rst | 74 +++++++++++-------- Lib/ast.py | 2 +- ...-02-28-11-51-51.gh-issue-116023.CGYhFh.rst | 5 +- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 5b3f0a0b20dafe..e31e7dd2fa636d 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -724,7 +724,7 @@ Comprehensions >>> print(ast.dump( ... ast.parse('[x for x in numbers]', mode='eval'), - ... indent=4, show_empty=True, + ... indent=4, ... )) Expression( body=ListComp( @@ -733,11 +733,10 @@ Comprehensions comprehension( target=Name(id='x', ctx=Store()), iter=Name(id='numbers', ctx=Load()), - ifs=[], is_async=0)])) >>> print(ast.dump( ... ast.parse('{x: x**2 for x in numbers}', mode='eval'), - ... indent=4, show_empty=True, + ... indent=4, ... )) Expression( body=DictComp( @@ -750,11 +749,10 @@ Comprehensions comprehension( target=Name(id='x', ctx=Store()), iter=Name(id='numbers', ctx=Load()), - ifs=[], is_async=0)])) >>> print(ast.dump( ... ast.parse('{x for x in numbers}', mode='eval'), - ... indent=4, show_empty=True, + ... indent=4, ... )) Expression( body=SetComp( @@ -763,7 +761,6 @@ Comprehensions comprehension( target=Name(id='x', ctx=Store()), iter=Name(id='numbers', ctx=Load()), - ifs=[], is_async=0)])) @@ -1404,7 +1401,7 @@ Pattern matching ... ... ... case tuple(): ... ... - ... """), indent=4, show_empty=True)) + ... """), indent=4)) Module( body=[ Match( @@ -1425,14 +1422,10 @@ Pattern matching value=Constant(value=Ellipsis))]), match_case( pattern=MatchClass( - cls=Name(id='tuple', ctx=Load()), - patterns=[], - kwd_attrs=[], - kwd_patterns=[]), + cls=Name(id='tuple', ctx=Load())), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1585,7 +1578,7 @@ Pattern matching ... ... ... case {**rest}: ... ... - ... """), indent=4, show_empty=True)) + ... """), indent=4)) Module( body=[ Match( @@ -1603,11 +1596,10 @@ Pattern matching Expr( value=Constant(value=Ellipsis))]), match_case( - pattern=MatchMapping(keys=[], patterns=[], rest='rest'), + pattern=MatchMapping(rest='rest'), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1636,7 +1628,7 @@ Pattern matching ... ... ... case Point3D(x=0, y=0, z=0): ... ... - ... """), indent=4, show_empty=True)) + ... """), indent=4)) Module( body=[ Match( @@ -1649,16 +1641,13 @@ Pattern matching MatchValue( value=Constant(value=0)), MatchValue( - value=Constant(value=0))], - kwd_attrs=[], - kwd_patterns=[]), + value=Constant(value=0))]), body=[ Expr( value=Constant(value=Ellipsis))]), match_case( pattern=MatchClass( cls=Name(id='Point3D', ctx=Load()), - patterns=[], kwd_attrs=[ 'x', 'y', @@ -1672,8 +1661,7 @@ Pattern matching value=Constant(value=0))]), body=[ Expr( - value=Constant(value=Ellipsis))])])], - type_ignores=[]) + value=Constant(value=Ellipsis))])])]) .. versionadded:: 3.10 @@ -1863,21 +1851,16 @@ Function and class definitions .. doctest:: - >>> print(ast.dump(ast.parse('lambda x,y: ...'), indent=4, show_empty=True)) + >>> print(ast.dump(ast.parse('lambda x,y: ...'), indent=4)) Module( body=[ Expr( value=Lambda( args=arguments( - posonlyargs=[], args=[ arg(arg='x'), - arg(arg='y')], - kwonlyargs=[], - kw_defaults=[], - defaults=[]), - body=Constant(value=Ellipsis)))], - type_ignores=[]) + arg(arg='y')]), + body=Constant(value=Ellipsis)))]) .. class:: arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults) @@ -2390,6 +2373,33 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.13 Added the *show_empty* option. + .. doctest:: + + >>> print(ast.dump(ast.parse("""\ + ... async def f(): + ... await other_func() + ... """), indent=4, show_empty=True)) + Module( + body=[ + AsyncFunctionDef( + name='f', + args=arguments( + posonlyargs=[], + args=[], + kwonlyargs=[], + kw_defaults=[], + defaults=[]), + body=[ + Expr( + value=Await( + value=Call( + func=Name(id='other_func', ctx=Load()), + args=[], + keywords=[])))], + decorator_list=[], + type_params=[])], + type_ignores=[]) + .. _ast-compiler-flags: diff --git a/Lib/ast.py b/Lib/ast.py index a56aa0572b0095..9f386051659e76 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -130,7 +130,7 @@ def dump( integer or string, then the tree will be pretty-printed with that indent level. None (the default) selects the single line representation. If show_empty is False, then empty lists and fields that are None - will be ommitted from the output for better readability. + will be omitted from the output for better readability. """ def _format(node, level=0): if indent is not None: diff --git a/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst index 5ee0db491700a9..bebb67e585eea6 100644 --- a/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst +++ b/Misc/NEWS.d/next/Library/2024-02-28-11-51-51.gh-issue-116023.CGYhFh.rst @@ -1,2 +1,3 @@ -Add ``show_empty=False`` parameter to :func:`ast.dump` to optionally enable -showing empty fields in the AST. +Don't show empty fields (value ``None`` or ``[]``) +in :func:`ast.dump` by default. Add ``show_empty=False`` +parameter to optionally show them. From ba78f924e7073a641281a42cc0fc5f261074abac Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 23 Apr 2024 00:19:39 +0300 Subject: [PATCH 10/10] Update Doc/library/ast.rst Co-authored-by: Carl Meyer --- Doc/library/ast.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index e31e7dd2fa636d..2a626c9049a8f5 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2364,8 +2364,8 @@ and classes for traversing abstract syntax trees: indents that many spaces per level. If *indent* is a string (such as ``"\t"``), that string is used to indent each level. - If *show_empty* is ``False``, then empty lists and fields that are ``None`` - will be omitted from the output for better readability. + If *show_empty* is ``False`` (the default), empty lists and fields that are ``None`` + will be omitted from the output. .. versionchanged:: 3.9 Added the *indent* option.