https://github.com/python/cpython/commit/b723c8be071afcf3f865c55a5efb6da54f7695a0
commit: b723c8be071afcf3f865c55a5efb6da54f7695a0
branch: main
author: Krzysztof Magusiak <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-07-31T12:55:00+03:00
summary:
gh-124503: Optimize ast.literal_eval() for small input (GH-137010)
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.
files:
A Misc/NEWS.d/next/Library/2025-07-30-11-12-22.gh-issue-124503.d4hc7b.rst
M Lib/ast.py
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..c04eba932a0f2e
--- /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` is 10-20% faster for small inputs.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]