Skip to main content

pylifecycle.c

pylifecycle.c owns every stage of interpreter birth and death. It wires together the runtime pre-init, the core PyInterpreterState setup, the site module import, and the orderly teardown sequence that runs atexit handlers, flushes stdio, and drives a final GC collection.

Map

LinesSymbolRole
1–80includes + staticsfeature guards, extern declarations, _Py_UnhandledKeyboardInterrupt
81–200_PyConfig_InitCompatConfigfills a PyConfig from the legacy Py_* global flags for Py_Initialize() callers
201–400pyinit_preinitpre-init: locale, memory allocators, hash randomization
401–700pyinit_corecore init: runtime state, main interpreter, codec bootstrap
701–1000pyinit_mainmain init: sys.path, site, readline, __main__
1001–1100Py_InitializeFromConfigpublic entry: calls pre-init, core, main in sequence
1101–1200Py_Initialize / Py_InitializeExlegacy wrappers via _PyConfig_InitCompatConfig
1201–1500Py_FinalizeExatexit, stdio flush, GC final collection, interp teardown
1501–1700finalize_interp_clear / finalize_interp_deleteper-interpreter cleanup steps
1701–2000Py_NewInterpreter / Py_NewInterpreterFromConfigsub-interpreter creation
2001–2100Py_EndInterpretersub-interpreter teardown
2101–2400signal handling helpersPyOS_InitInterrupts, _Py_HandleSystemExit
2401–2800misc helpersPy_AtExit, _Py_FatalError, Py_ExitStatusException

Reading

Three-phase initialization

Py_InitializeFromConfig sequences three internal phases. Each phase returns _PyStatus so an early error propagates cleanly without unwinding a partially initialized interpreter.

// CPython: Python/pylifecycle.c:1042 Py_InitializeFromConfig
PyStatus
Py_InitializeFromConfig(const PyConfig *config) {
PyStatus status;
status = pyinit_preinit(config); /* locale, alloc, hash seed */
if (_PyStatus_EXCEPTION(status)) { return status; }
status = pyinit_core(config); /* runtime, interp, codecs */
if (_PyStatus_EXCEPTION(status)) { return status; }
status = pyinit_main(config); /* site, readline, __main__ */
return status;
}

Legacy compatibility shim

Py_Initialize() predates PyConfig. CPython bridges it through _PyConfig_InitCompatConfig, which reads the old Py_VerboseFlag, Py_NoSiteFlag, etc. global ints and writes them into a fresh PyConfig.

// CPython: Python/pylifecycle.c:112 _PyConfig_InitCompatConfig
void _PyConfig_InitCompatConfig(PyConfig *config) {
PyConfig_InitPythonConfig(config);
config->verbose = Py_VerboseFlag;
config->optimization_level = Py_OptimizeFlag;
config->site_import = !Py_NoSiteFlag;
/* ... more flag mappings ... */
}

Finalization order

Py_FinalizeEx must drain side effects before releasing memory. The ordering is critical: atexit callbacks can allocate objects, so GC must still be live when they run.

// CPython: Python/pylifecycle.c:1230 Py_FinalizeEx
int Py_FinalizeEx(void) {
_Py_FinalizeAtExit(); /* run atexit callbacks */
_PyErr_CheckSignals_Finalize(); /* deliver pending signals */
flush_std_files(); /* sys.stdout / sys.stderr */
_PyGC_CollectNoFail(tstate); /* final GC cycle */
finalize_interp_clear(tstate); /* clear dicts, builtins, codecs */
finalize_interp_delete(tstate); /* free PyInterpreterState */
_PyRuntime_Finalize(); /* free _PyRuntimeState */
return 0;
}

Sub-interpreter lifecycle

Py_NewInterpreterFromConfig creates an independent PyInterpreterState with its own sys.modules, GC state, and optional GIL (PEP 684). The caller must already hold the GIL of an existing interpreter.

// CPython: Python/pylifecycle.c:1752 Py_NewInterpreterFromConfig
PyStatus
Py_NewInterpreterFromConfig(PyThreadState **tstate_p,
const PyInterpreterConfig *config)
{
PyInterpreterState *interp = PyInterpreterState_New();
/* ... configure, init modules, import importlib ... */
*tstate_p = tstate;
return _PyStatus_OK();
}

gopy notes

gopy's analog to the three-phase init lives in pythonrun/runstring.go and the vm.VM constructor. The preinit phase (locale, allocator) has no equivalent because Go manages memory and locale separately.

Py_FinalizeEx teardown order maps to vm.VM.Close() (not yet fully ported). The atexit mechanism is tracked in the module/ layer. Sub-interpreter support (Py_NewInterpreterFromConfig) is out of scope for the current v0.12.1 milestone.

CPython 3.14 changes

  • Py_NewInterpreterFromConfig and PyInterpreterConfig were added in 3.12 (PEP 684) and refined in 3.13 to expose allow_threads / own_gil. In 3.14 the struct gained allow_exec to restrict exec() in sub-interpreters.
  • _PyConfig_InitCompatConfig was renamed from _Py_InitializeFromConfig internals in 3.11; signature is stable through 3.14.
  • flush_std_files became a separate static function in 3.11; 3.14 adds a check for sys.stdout being replaced by a non-file object before calling PyFile_WriteString.