Skip to main content

pythonrun.c: parse-compile-eval pipeline

Python/pythonrun.c is the top-level execution engine for string and file inputs. It glues the parser, compiler, and eval loop together, and handles exception printing and SystemExit unwinding.

Map

LinesSymbolRole
76–120PyRun_StringFlagsEntry point: parse a C string into an AST, compile it, then call run_mod
121–160PyRun_FileExFlagsFile-path variant; opens the file and delegates to PyRun_FileObject
161–210run_modAllocates a PyArena, compiles the mod node to a code object, then calls run_pyc_file or run_eval_code_obj
211–260run_eval_code_objBuilds the global/local dicts and calls PyEval_EvalCode
261–310handle_system_exitChecks for SystemExit, extracts the exit code, and calls Py_Exit
311–420PyErr_PrintExFormats a traceback using PyException_GetTraceback and writes it to sys.stderr
421–500print_exceptionFormats a single exception frame; calls PyFile_WriteObject for each line
501–560_PyObject_CallMethodIdObjArgsThin helper that resolves a method by _Py_Identifier and forwards variadic args
561–650flush_ioFlushes sys.stdout and sys.stderr after execution; swallows IOError

Reading

PyRun_StringFlags and run_mod

PyRun_StringFlags is the canonical entry point for exec/eval from C. It calls PyParser_ASTFromStringObject to get a mod_ty node, then hands off to run_mod:

// Python/pythonrun.c:76 PyRun_StringFlags
PyObject *
PyRun_StringFlags(const char *str, int start, PyObject *globals,
PyObject *locals, PyCompilerFlags *flags)
{
PyObject *ret = NULL;
mod_ty mod;
PyArena *arena;
...
arena = _PyArena_New();
mod = PyParser_ASTFromStringObject(str, filename, start, flags, arena);
if (mod != NULL)
ret = run_mod(mod, filename, globals, locals, flags, arena);
_PyArena_Free(arena);
return ret;
}

run_mod calls PyAST_CompileObject to produce a PyCodeObject, then calls run_eval_code_obj. The arena owns all AST nodes and is freed after compilation, so the code object holds no dangling pointers.

handle_system_exit and exception printing

When the eval loop raises SystemExit, handle_system_exit extracts args[0] from the exception value. If it is an integer, that becomes the process exit code. If it is None, the code is 0. Any other object is printed to sys.stderr before exit:

// Python/pythonrun.c:261 handle_system_exit
static void
handle_system_exit(void)
{
PyObject *exc = PyErr_GetRaisedException();
...
exitcode = PyException_GetArgs(exc);
if (PyLong_Check(exitcode))
exitval = (int)PyLong_AsLong(exitcode);
...
Py_Exit(exitval);
}

PyErr_PrintEx calls print_exception for each chained exception, writing to sys.stderr via PyFile_WriteObject. In 3.14 the function gained a colorize parameter that wraps ANSI escape codes around tracebacks when stderr is a tty.

3.14 changes

CPython 3.14 introduced _colorize.py and a C helper _PyErr_WriteUnraisableMsgWithContext that routes unraisable exceptions through the new colorized formatter. The flush_io call was also tightened to catch Exception rather than bare BaseException, so a KeyboardInterrupt during flush still propagates.

gopy notes

pythonrun/runstring.go covers PyRun_StringFlags and run_mod for the string input path. The file-based variants (PyRun_FileExFlags, run_pyc_file) are not yet ported. handle_system_exit is partially handled in vm/eval_unwind.go via errors.IsSystemExit. PyErr_PrintEx is not ported; tracebacks are currently formatted by objects/str.go helpers. The 3.14 colorize path is out of scope for v0.12.1.