Skip to main content

Python/Python-ast.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Python/Python-ast.c

This annotation covers AST node creation and traversal. See modules_ast2_detail for ast.dump, ast.fix_missing_locations, and ast.NodeVisitor.

Map

LinesSymbolRole
1-80AST node constructorsast.BinOp(...), ast.Call(...) etc.
81-180ast.parseParse source to AST
181-280ast.unparseConvert AST back to source string
281-380ast.NodeTransformerMutating tree visitor
381-500ast.literal_evalSafely evaluate a literal expression

Reading

ast.parse

# CPython: Lib/ast.py:60 parse
def parse(source, filename='<unknown>', mode='exec', *,
type_comments=False, feature_version=None, optimize=-1):
"""Parse source into an AST node.
mode: 'exec' (module), 'eval' (expression), 'single' (interactive)."""
flags = ast.PyCF_ONLY_AST
if type_comments:
flags |= ast.PyCF_TYPE_COMMENTS
return compile(source, filename, mode, flags, optimize=optimize)

ast.parse is a thin wrapper around compile with PyCF_ONLY_AST set. The compiler's parser stage returns the AST instead of proceeding to bytecode generation.

ast.unparse

# CPython: Lib/ast.py:480 unparse
def unparse(ast_obj):
"""Unparse an AST object and generate a string with code
that would produce an equivalent ast.AST object if parsed back."""
unparser = _Unparser()
return unparser.visit(ast_obj)

class _Unparser(NodeVisitor):
def visit_BinOp(self, node):
self.traverse(node.left)
self.write(f' {_bin_ops[type(node.op)]} ')
self.traverse(node.right)

ast.unparse reconstructs Python source from an AST. It is not guaranteed to be identical to the original source (whitespace, comments are lost) but round-trips correctly: ast.parse(ast.unparse(tree)) gives an equivalent AST.

ast.literal_eval

# CPython: Lib/ast.py:60 literal_eval
def literal_eval(node_or_string):
"""Safely evaluate an expression containing only literals:
strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None.
Raises ValueError for anything else."""
if isinstance(node_or_string, str):
node_or_string = parse(node_or_string, mode='eval')
...
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))
...
raise ValueError('malformed node or string')
return _convert(node_or_string.body)

ast.literal_eval evaluates without calling eval. It is safe to use on untrusted input because it only handles literals. eval executes arbitrary code.

gopy notes

ast.parse is module/ast.Parse in module/ast/module.go. It calls compile.ParseModule or compile.ParseExpression. ast.unparse is module/ast.Unparse using a recursive visitor. ast.literal_eval is module/ast.LiteralEval walking the AST recursively with a type check at each node.