https://github.com/python/cpython/commit/31343cf2bc5f7209fa965558265555973323c2f9
commit: 31343cf2bc5f7209fa965558265555973323c2f9
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-03-04T11:00:08+01:00
summary:

gh-142417: Restore private _Py_InitializeMain() function (#145472)

This reverts commit 07c3518ffb27875b14a0f1637aa85f773ff2f9ff.

Co-authored-by: Petr Viktorin <[email protected]>

files:
A Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst
M Doc/c-api/init_config.rst
M Doc/whatsnew/3.15.rst
M Include/cpython/pylifecycle.h
M Lib/test/test_embed.py
M Programs/_testembed.c
M Python/pylifecycle.c

diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index a143274bfe69e2..f6dc604a609cb1 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -2299,13 +2299,91 @@ 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 <init-path-config>`;
+  * 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:: int 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 <init-path-config>` is only
+applied during the "Main" phase. It may allow to customize Python in Python to
+override or tune the :ref:`Path Configuration <init-path-config>`, maybe
+install a custom :data:`sys.meta_path` importer or an import hook, etc.
+
+It may become possible to calculate the :ref:`Path Configuration
+<init-path-config>` 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)
 {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to