]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142417: Restore private _Py_InitializeMain() function (#145472)
authorVictor Stinner <vstinner@python.org>
Wed, 4 Mar 2026 10:00:08 +0000 (11:00 +0100)
committerGitHub <noreply@github.com>
Wed, 4 Mar 2026 10:00:08 +0000 (11:00 +0100)
This reverts commit 07c3518ffb27875b14a0f1637aa85f773ff2f9ff.

Co-authored-by: Petr Viktorin <encukou@gmail.com>
Doc/c-api/init_config.rst
Doc/whatsnew/3.15.rst
Include/cpython/pylifecycle.h
Lib/test/test_embed.py
Misc/NEWS.d/next/C_API/2026-03-03-14-59-57.gh-issue-142417.HiNP5j.rst [new file with mode: 0644]
Programs/_testembed.c
Python/pylifecycle.c

index a143274bfe69e2f6bf3ad168c30f7f56bef73d5f..f6dc604a609cb1da0c93d947c7fa1b97d68f54e7 100644 (file)
@@ -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);
+        }
+    }
index 63ef5f84301794dd3c9509db2d658838276009a9..fff2168be726047bac2f3c5b77e6b835a86847a4 100644 (file)
@@ -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
 --------------
 
index 86ce6e6f79824a76aeb0d8c0903d379757c09ece..e46dfe59ec463044b06d3065dd3e4bfb26821e38 100644 (file)
@@ -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);
 
 
index b3f0cb5d35de5d9da89a8f2a9efd744b9aa59690..45d0d8308dbdea6b5cdc1baf82d735a70086e5fb 100644 (file)
@@ -1319,6 +1319,24 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         }
         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 (file)
index 0000000..943be5b
--- /dev/null
@@ -0,0 +1,2 @@
+Restore private provisional ``_Py_InitializeMain()`` function removed in
+Python 3.14. Patch by Victor Stinner.
index 38f546b976cac3acf75e99484ef08e94b989b21b..d4d2a7131ccb1f0048713275c33739a2f4390de9 100644 (file)
@@ -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},
index 7dfeb5b847b25438b63a73b9664d92e3b450fd7b..711e7bc89b71c04a5cc038c9008da3226986b485 100644 (file)
@@ -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)
 {