From 125a4c6866f61f681f8290f972984d1c7ff56e2e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 3 Mar 2026 14:50:17 +0100 Subject: [PATCH 1/2] gh-142417: Restore private _Py_InitializeMain() function This reverts commit 07c3518ffb27875b14a0f1637aa85f773ff2f9ff. --- Doc/c-api/init_config.rst | 92 +++++++++++++++++-- Doc/whatsnew/3.15.rst | 4 + Include/cpython/pylifecycle.h | 3 + Lib/test/test_embed.py | 18 ++++ ...-03-03-14-59-57.gh-issue-142417.HiNP5j.rst | 2 + Programs/_testembed.c | 28 ++++++ Python/pylifecycle.c | 12 +++ 7 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index a143274bfe69e2..2c0d6c810f7353 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -2299,13 +2299,89 @@ Py_GetArgcArgv() See also :c:member:`PyConfig.orig_argv` member. -Delaying main module execution -============================== -In some embedding use cases, it may be desirable to separate interpreter initialization -from the execution of the main module. +Multi-Phase Initialization Private Provisional API +================================================== -This separation can be achieved by setting ``PyConfig.run_command`` to the empty -string during initialization (to prevent the interpreter from dropping into the -interactive prompt), and then subsequently executing the desired main module -code using ``__main__.__dict__`` as the global namespace. +This section is a private provisional API introducing multi-phase +initialization, the core feature of :pep:`432`: + +* "Core" initialization phase, "bare minimum Python": + + * Builtin types; + * Builtin exceptions; + * Builtin and frozen modules; + * The :mod:`sys` module is only partially initialized + (ex: :data:`sys.path` doesn't exist yet). + +* "Main" initialization phase, Python is fully initialized: + + * Install and configure :mod:`importlib`; + * Apply the :ref:`Path Configuration `; + * Install signal handlers; + * Finish :mod:`sys` module initialization (ex: create :data:`sys.stdout` + and :data:`sys.path`); + * Enable optional features like :mod:`faulthandler` and :mod:`tracemalloc`; + * Import the :mod:`site` module; + * etc. + +Private provisional API: + +* :c:member:`PyConfig._init_main`: if set to ``0``, + :c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase. + +.. c:function:: PyStatus _Py_InitializeMain(void) + + Move to the "Main" initialization phase, finish the Python initialization. + +No module is imported during the "Core" phase and the ``importlib`` module is +not configured: the :ref:`Path Configuration ` is only +applied during the "Main" phase. It may allow to customize Python in Python to +override or tune the :ref:`Path Configuration `, maybe +install a custom :data:`sys.meta_path` importer or an import hook, etc. + +It may become possible to calculate the :ref:`Path Configuration +` in Python, after the Core phase and before the Main phase, +which is one of the :pep:`432` motivation. + +The "Core" phase is not properly defined: what should be and what should +not be available at this phase is not specified yet. The API is marked +as private and provisional: the API can be modified or even be removed +anytime until a proper public API is designed. + +Example running Python code between "Core" and "Main" initialization +phases:: + + void init_python(void) + { + PyStatus status; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + config._init_main = 0; + + /* ... customize 'config' configuration ... */ + + status = Py_InitializeFromConfig(&config); + PyConfig_Clear(&config); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + + /* Use sys.stderr because sys.stdout is only created + by _Py_InitializeMain() */ + int res = PyRun_SimpleString( + "import sys; " + "print('Run Python code before _Py_InitializeMain', " + "file=sys.stderr)"); + if (res < 0) { + exit(1); + } + + /* ... put more configuration code here ... */ + + status = _Py_InitializeMain(); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + } diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 63ef5f84301794..fff2168be72604 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1664,6 +1664,10 @@ New features * Add :c:func:`PyUnstable_SetImmortal` C-API function to mark objects as :term:`immortal`. (Contributed by Kumar Aditya in :gh:`143300`.) +* Restore private provisional ``_Py_InitializeMain()`` function removed in + Python 3.14. + (Contributed by Victor Stinner in :gh:`142417`.) + Changed C APIs -------------- diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h index 86ce6e6f79824a..e46dfe59ec4630 100644 --- a/Include/cpython/pylifecycle.h +++ b/Include/cpython/pylifecycle.h @@ -25,6 +25,9 @@ PyAPI_FUNC(PyStatus) Py_PreInitializeFromArgs( PyAPI_FUNC(PyStatus) Py_InitializeFromConfig( const PyConfig *config); +// Python 3.8 provisional API (PEP 587) +PyAPI_FUNC(PyStatus) _Py_InitializeMain(void); + PyAPI_FUNC(int) Py_RunMain(void); diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index b3f0cb5d35de5d..45d0d8308dbdea 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1319,6 +1319,24 @@ def test_init_run_main(self): } self.check_all_configs("test_init_run_main", config, api=API_PYTHON) + def test_init_main(self): + code = ('import _testinternalcapi, json; ' + 'print(json.dumps(_testinternalcapi.get_configs()))') + config = { + 'argv': ['-c', 'arg2'], + 'orig_argv': ['python3', + '-c', code, + 'arg2'], + 'program_name': './python3', + 'run_command': code + '\n', + 'parse_argv': True, + '_init_main': False, + 'sys_path_0': '', + } + self.check_all_configs("test_init_main", config, + api=API_PYTHON, + stderr="Run Python code before _Py_InitializeMain") + def test_init_parse_argv(self): config = { 'parse_argv': True, diff --git a/Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst b/Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst new file mode 100644 index 00000000000000..943be5b4533d9e --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst @@ -0,0 +1,2 @@ +Restore private provisional ``_Py_InitializeMain()`` function removed in +Python 3.14. Patch by Victor Stinner. diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 38f546b976cac3..d4d2a7131ccb1f 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1991,6 +1991,33 @@ static int test_init_run_main(void) } +static int test_init_main(void) +{ + PyConfig config; + PyConfig_InitPythonConfig(&config); + + configure_init_main(&config); + config._init_main = 0; + init_from_config_clear(&config); + + /* sys.stdout don't exist yet: it is created by _Py_InitializeMain() */ + int res = PyRun_SimpleString( + "import sys; " + "print('Run Python code before _Py_InitializeMain', " + "file=sys.stderr)"); + if (res < 0) { + exit(1); + } + + PyStatus status = _Py_InitializeMain(); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + + return Py_RunMain(); +} + + static int test_run_main(void) { PyConfig config; @@ -2649,6 +2676,7 @@ static struct TestCase TestCases[] = { {"test_preinit_parse_argv", test_preinit_parse_argv}, {"test_preinit_dont_parse_argv", test_preinit_dont_parse_argv}, {"test_init_run_main", test_init_run_main}, + {"test_init_main", test_init_main}, {"test_init_sys_add", test_init_sys_add}, {"test_init_setpath", test_init_setpath}, {"test_init_setpath_config", test_init_setpath_config}, diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7dfeb5b847b254..711e7bc89b71c0 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1530,6 +1530,18 @@ Py_Initialize(void) } +PyStatus +_Py_InitializeMain(void) +{ + PyStatus status = _PyRuntime_Initialize(); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + PyThreadState *tstate = _PyThreadState_GET(); + return pyinit_main(tstate); +} + + static void finalize_modules_delete_special(PyThreadState *tstate, int verbose) { From 8da4daa63efa261a5258832954fab8ba758934f3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 3 Mar 2026 17:12:54 +0100 Subject: [PATCH 2/2] Update Doc/c-api/init_config.rst Co-authored-by: Petr Viktorin --- Doc/c-api/init_config.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 2c0d6c810f7353..f6dc604a609cb1 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -2327,8 +2327,10 @@ initialization, the core feature of :pep:`432`: Private provisional API: -* :c:member:`PyConfig._init_main`: if set to ``0``, - :c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase. +.. c:member:: int PyConfig._init_main + + If set to ``0``, :c:func:`Py_InitializeFromConfig` stops at the "Core" + initialization phase. .. c:function:: PyStatus _Py_InitializeMain(void)