From 01afd3d3e966ee33b76b53a11bc73a2e555313c5 Mon Sep 17 00:00:00 2001 From: Krzysztof Magusiak Date: Tue, 22 Jul 2025 16:56:57 +0200 Subject: [PATCH 1/2] gh-124503: simplified ast.literal_eval The implementation does not create anymore local functions which reduces the overhead for small inputs. Some other calls are inlined into a single `_convert_literal` function. We have a gain of 10-20% for small inputs and only 1-2% for bigger inputs. --- Lib/ast.py | 97 ++++++++++--------- ...-07-30-11-12-22.gh-issue-124503.d4hc7b.rst | 1 + 2 files changed, 53 insertions(+), 45 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst diff --git a/Lib/ast.py b/Lib/ast.py index 6d3daf64f5c6d7..983ac1710d0205 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -57,53 +57,60 @@ def literal_eval(node_or_string): Caution: A complex expression can overflow the C stack and cause a crash. """ if isinstance(node_or_string, str): - node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval') - if isinstance(node_or_string, Expression): + node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval').body + elif isinstance(node_or_string, Expression): node_or_string = node_or_string.body - def _raise_malformed_node(node): - msg = "malformed node or string" - if lno := getattr(node, 'lineno', None): - msg += f' on line {lno}' - raise ValueError(msg + f': {node!r}') - def _convert_num(node): - if not isinstance(node, Constant) or type(node.value) not in (int, float, complex): - _raise_malformed_node(node) + return _convert_literal(node_or_string) + + +def _convert_literal(node): + """ + Used by `literal_eval` to convert an AST node into a value. + """ + if isinstance(node, Constant): return node.value - def _convert_signed_num(node): - if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)): - operand = _convert_num(node.operand) - if isinstance(node.op, UAdd): - return + operand - else: - return - operand - return _convert_num(node) - def _convert(node): - if isinstance(node, Constant): - return node.value - elif isinstance(node, Tuple): - return tuple(map(_convert, node.elts)) - elif isinstance(node, List): - return list(map(_convert, node.elts)) - elif isinstance(node, Set): - return set(map(_convert, node.elts)) - elif (isinstance(node, Call) and isinstance(node.func, Name) and - node.func.id == 'set' and node.args == node.keywords == []): - return set() - elif isinstance(node, Dict): - if len(node.keys) != len(node.values): - _raise_malformed_node(node) - return dict(zip(map(_convert, node.keys), - map(_convert, node.values))) - elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)): - left = _convert_signed_num(node.left) - right = _convert_num(node.right) - if isinstance(left, (int, float)) and isinstance(right, complex): - if isinstance(node.op, Add): - return left + right - else: - return left - right - return _convert_signed_num(node) - return _convert(node_or_string) + if isinstance(node, Dict) and len(node.keys) == len(node.values): + return dict(zip( + map(_convert_literal, node.keys), + map(_convert_literal, node.values), + )) + if isinstance(node, Tuple): + return tuple(map(_convert_literal, node.elts)) + if isinstance(node, List): + return list(map(_convert_literal, node.elts)) + if isinstance(node, Set): + return set(map(_convert_literal, node.elts)) + if ( + isinstance(node, Call) and isinstance(node.func, Name) + and node.func.id == 'set' and node.args == node.keywords == [] + ): + return set() + if ( + isinstance(node, UnaryOp) + and isinstance(node.op, (UAdd, USub)) + and isinstance(node.operand, Constant) + and type(operand := node.operand.value) in (int, float, complex) + ): + if isinstance(node.op, UAdd): + return + operand + else: + return - operand + if ( + isinstance(node, BinOp) + and isinstance(node.op, (Add, Sub)) + and isinstance(node.left, (Constant, UnaryOp)) + and isinstance(node.right, Constant) + and type(left := _convert_literal(node.left)) in (int, float) + and type(right := _convert_literal(node.right)) is complex + ): + if isinstance(node.op, Add): + return left + right + else: + return left - right + msg = "malformed node or string" + if lno := getattr(node, 'lineno', None): + msg += f' on line {lno}' + raise ValueError(msg + f': {node!r}') def dump( diff --git a/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst b/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst new file mode 100644 index 00000000000000..3114dd22856c00 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst @@ -0,0 +1 @@ +:func:`ast.literal_eval` simplified to avoid local functions. From 9e2f796d285d3dde7d2b12e2cf78cc7f975fdaaa Mon Sep 17 00:00:00 2001 From: Krzysztof Magusiak Date: Wed, 30 Jul 2025 13:50:11 +0200 Subject: [PATCH 2/2] gh-124503: update news --- .../next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst b/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst index 3114dd22856c00..c04eba932a0f2e 100644 --- a/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst +++ b/Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst @@ -1 +1 @@ -:func:`ast.literal_eval` simplified to avoid local functions. +:func:`ast.literal_eval` is 10-20% faster for small inputs. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy