Skip to main content

Python-ast.c — generated AST node types

Python/Python-ast.c is machine-generated by running Parser/asdl_c.py against Parser/Python.asdl. It defines every AST node type as a Python PyTypeObject, wires up field names and attributes, and exports the ast module's public namespace. Do not edit this file by hand; edit the ASDL grammar or the code generator instead.

Map

LinesSymbolRole
1–80header comment, includesGeneration timestamp, _ast.h include
81–300ast_type_reduce, ast_type_fieldsPickling support for all node types
301–700ast_new_impl, ast_init_implast.AST.__new__ and __init__
701–1500Module, Interactive, ExpressionTop-level mod node types
1501–4500stmt subtypes: FunctionDef through ExprStatement node registrations
4501–6500expr subtypes: BoolOp through ConstantExpression node registrations
6501–7500pattern, excepthandler, arg, etc.Remaining grammar categories
7501–8000_PyAST_Init, PyInit__astModule init and type finalisation

Reading

ast.AST base class and __reduce__

Every node inherits from a single C base type. The ast_type_reduce function lets nodes survive pickle.dumps by reconstructing them from their field values.

// CPython: Python/Python-ast.c:112 ast_type_reduce
static PyObject *
ast_type_reduce(PyObject *self, PyObject *unused)
{
PyObject *dict;
/* __dict__ may hold extra attributes set by the user. */
if (_PyObject_GetMethod(self, &_Py_ID(__dict__), &dict) == 0) {
return PyTuple_Pack(3,
Py_TYPE(self),
_PyTuple_FromArray(NULL, 0),
dict);
}
Py_CLEAR(dict);
return PyTuple_Pack(2,
Py_TYPE(self),
_PyTuple_FromArray(NULL, 0));
}

The three-element tuple form (type, args, state) is the standard __reduce__ protocol. state here is the node's __dict__, which carries any annotator-added attributes such as lineno overrides or type-checker metadata.

Field registration for a statement node

Each concrete node type calls a shared helper to register its field names. Here is the pattern for FunctionDef, whose ASDL definition is:

FunctionDef(identifier name, arguments args, stmt* body,
expr* decorator_list, expr? returns, string? type_comment,
type_param* type_params)

The generated C code builds a static PyMemberDef-style field table:

// CPython: Python/Python-ast.c:1620 FunctionDef_fields
static const char * const FunctionDef_fields[] = {
"name", "args", "body", "decorator_list",
"returns", "type_comment", "type_params",
};

static int
FunctionDef_init(PyObject *self, PyObject *args, PyObject *kw)
{
return ast_type_init(self, args, kw,
FunctionDef_fields,
Py_ARRAY_LENGTH(FunctionDef_fields));
}

ast_type_init stores each keyword argument into self.__dict__ under the matching field name, which is how ast.parse() populates nodes after the C-level parser fills in the ASDL structs.

Attribute vs field distinction

Every stmt and expr node also carries four positional attributes (lineno, col_offset, end_lineno, end_col_offset) that are not in _fields_ but in _attributes_. The generated code registers them separately so that tools like ast.fix_missing_locations can iterate attributes independently.

// CPython: Python/Python-ast.c:203 ast_attributes
static const char * const stmt_attributes[] = {
"lineno", "col_offset", "end_lineno", "end_col_offset",
};

This separation is why ast.walk only descends into _fields_ values and never recurses into lineno.

Module init and type finalisation

_PyAST_Init is called once during interpreter startup. It finalises every PyTypeObject in dependency order (base before subtype) and installs them all into the _ast module dict.

// CPython: Python/Python-ast.c:7820 _PyAST_Init
int
_PyAST_Init(PyInterpreterState *interp)
{
struct ast_state *state = &interp->ast;
if (state->initialized) return 0;

if (init_types(state) < 0) return -1;
state->initialized = 1;
return 0;
}

init_types calls PyType_Ready for each of the roughly 120 node types defined in the ASDL grammar. The resulting type objects are singletons for the lifetime of the interpreter.

gopy notes

gopy does not need to port Python-ast.c in full; the Go compiler pipeline builds its own IR. However, two patterns here are directly relevant.

First, the ast_type_reduce design matches how gopy should implement pickling support for objects that carry a __dict__: pack (type, (), __dict__) and let __setstate__ restore from the third element.

Second, the field-vs-attribute split (_fields_ / _attributes_) is worth adopting for any gopy type that mixes structural children with metadata. Keeping metadata out of _fields_ prevents ast.walk-style traversals from accidentally descending into non-node values.

CPython 3.14 changes

  • type_params was added to FunctionDef, AsyncFunctionDef, and ClassDef in 3.12 (PEP 695 type parameter syntax). The field is stable in 3.14.
  • Python-ast.c is now regenerated as part of the standard make regen-ast target; the file is checked into the repository for platforms that cannot run the generator.
  • In 3.13 the per-interpreter ast_state struct replaced the old process-global statics, enabling sub-interpreter isolation for AST type objects.
  • 3.14 adds PEP 701 f-string node changes: JoinedStr children can now be FormattedValue or plain string Constant nodes interleaved, matching the new tokeniser output.