Skip to content

Commit a0cd290

Browse files
committed
pythongh-142417: Add Py_InitializeMain() function
Rename also PyConfig._init_main to PyConfig.init_main.
1 parent c461aa9 commit a0cd290

File tree

10 files changed

+167
-15
lines changed

10 files changed

+167
-15
lines changed

Doc/c-api/init_config.rst

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,15 @@ PyConfig
15521552
15531553
.. versionadded:: 3.13
15541554
1555+
.. c:member:: int init_main
1556+
1557+
If set to ``0``, :c:func:`Py_InitializeFromConfig` stops at the "Core"
1558+
initialization phase.
1559+
1560+
Default: ``1``.
1561+
1562+
.. versionadded:: 3.15
1563+
15551564
.. c:member:: int isolated
15561565
15571566
If greater than ``0``, enable isolated mode:
@@ -2299,13 +2308,86 @@ Py_GetArgcArgv()
22992308
23002309
See also :c:member:`PyConfig.orig_argv` member.
23012310
2302-
Delaying main module execution
2311+
2312+
Multi-Phase Initialization API
23032313
==============================
23042314
2305-
In some embedding use cases, it may be desirable to separate interpreter initialization
2306-
from the execution of the main module.
2315+
This section is an API introducing multi-phase
2316+
initialization, the core feature of :pep:`432`:
2317+
2318+
* "Core" initialization phase, "bare minimum Python":
2319+
2320+
* Builtin types;
2321+
* Builtin exceptions;
2322+
* Builtin and frozen modules;
2323+
* The :mod:`sys` module is only partially initialized
2324+
(ex: :data:`sys.path` doesn't exist yet).
2325+
2326+
* "Main" initialization phase, Python is fully initialized:
2327+
2328+
* Install and configure :mod:`importlib`;
2329+
* Apply the :ref:`Path Configuration <init-path-config>`;
2330+
* Install signal handlers;
2331+
* Finish :mod:`sys` module initialization (ex: create :data:`sys.stdout`
2332+
and :data:`sys.path`);
2333+
* Enable optional features like :mod:`faulthandler` and :mod:`tracemalloc`;
2334+
* Import the :mod:`site` module;
2335+
* etc.
2336+
2337+
.. c:function:: PyStatus Py_InitializeMain(void)
2338+
2339+
Move to the "Main" initialization phase, finish the Python initialization.
2340+
2341+
.. versionadded:: 3.15
2342+
2343+
No module is imported during the "Core" phase and the ``importlib`` module is
2344+
not configured: the :ref:`Path Configuration <init-path-config>` is only
2345+
applied during the "Main" phase. It may allow to customize Python in Python to
2346+
override or tune the :ref:`Path Configuration <init-path-config>`, maybe
2347+
install a custom :data:`sys.meta_path` importer or an import hook, etc.
2348+
2349+
It may become possible to calculate the :ref:`Path Configuration
2350+
<init-path-config>` in Python, after the Core phase and before the Main phase,
2351+
which is one of the :pep:`432` motivation.
2352+
2353+
The "Core" phase is not properly defined: what should be and what should
2354+
not be available at this phase is not specified yet. The API is marked
2355+
as private and provisional: the API can be modified or even be removed
2356+
anytime until a proper public API is designed.
2357+
2358+
Example running Python code between "Core" and "Main" initialization
2359+
phases::
2360+
2361+
void init_python(void)
2362+
{
2363+
PyStatus status;
2364+
2365+
PyConfig config;
2366+
PyConfig_InitPythonConfig(&config);
2367+
config.init_main = 0;
2368+
2369+
/* ... customize 'config' configuration ... */
23072370
2308-
This separation can be achieved by setting ``PyConfig.run_command`` to the empty
2309-
string during initialization (to prevent the interpreter from dropping into the
2310-
interactive prompt), and then subsequently executing the desired main module
2311-
code using ``__main__.__dict__`` as the global namespace.
2371+
status = Py_InitializeFromConfig(&config);
2372+
PyConfig_Clear(&config);
2373+
if (PyStatus_Exception(status)) {
2374+
Py_ExitStatusException(status);
2375+
}
2376+
2377+
/* Use sys.stderr because sys.stdout is only created
2378+
by Py_InitializeMain() */
2379+
int res = PyRun_SimpleString(
2380+
"import sys; "
2381+
"print('Run Python code before Py_InitializeMain', "
2382+
"file=sys.stderr)");
2383+
if (res < 0) {
2384+
exit(1);
2385+
}
2386+
2387+
/* ... put more configuration code here ... */
2388+
2389+
status = Py_InitializeMain();
2390+
if (PyStatus_Exception(status)) {
2391+
Py_ExitStatusException(status);
2392+
}
2393+
}

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,11 @@ C API changes
12201220
New features
12211221
------------
12221222

1223+
* Add :c:func:`Py_InitializeMain` function to move to the "Main" initialization
1224+
phase, finish the Python initialization. Rename also
1225+
:c:member:`!PyConfig._init_main` to :c:member:`PyConfig.init_main`.
1226+
(Contributed by Victor Stinner in :gh:`142417`.)
1227+
12231228
* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
12241229
:c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString`
12251230
functions as replacements for :c:func:`PySys_GetObject`.

Include/cpython/initconfig.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ typedef struct PyConfig {
191191
int tlbc_enabled;
192192
#endif
193193

194+
// If equal to 0, stop Python initialization before the "main" phase.
195+
int init_main;
196+
194197
/* --- Path configuration inputs ------------ */
195198
int pathconfig_warnings;
196199
wchar_t *program_name;
@@ -224,9 +227,6 @@ typedef struct PyConfig {
224227
// Needed by freeze_importlib.
225228
int _install_importlib;
226229

227-
// If equal to 0, stop Python initialization before the "main" phase.
228-
int _init_main;
229-
230230
// If non-zero, we believe we're running from a source tree.
231231
int _is_python_build;
232232

Include/cpython/pylifecycle.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ PyAPI_FUNC(PyStatus) Py_PreInitializeFromArgs(
2525
PyAPI_FUNC(PyStatus) Py_InitializeFromConfig(
2626
const PyConfig *config);
2727

28+
PyAPI_FUNC(PyStatus) Py_InitializeMain(void);
29+
2830
PyAPI_FUNC(int) Py_RunMain(void);
2931

3032

Lib/test/test_embed.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
696696
'_install_importlib': True,
697697
'check_hash_pycs_mode': 'default',
698698
'pathconfig_warnings': True,
699-
'_init_main': True,
699+
'init_main': True,
700700
'use_frozen_modules': not support.Py_DEBUG,
701701
'safe_path': False,
702702
'_is_python_build': IGNORE_CONFIG,
@@ -1314,6 +1314,24 @@ def test_init_run_main(self):
13141314
}
13151315
self.check_all_configs("test_init_run_main", config, api=API_PYTHON)
13161316

1317+
def test_init_main(self):
1318+
code = ('import _testinternalcapi, json; '
1319+
'print(json.dumps(_testinternalcapi.get_configs()))')
1320+
config = {
1321+
'argv': ['-c', 'arg2'],
1322+
'orig_argv': ['python3',
1323+
'-c', code,
1324+
'arg2'],
1325+
'program_name': './python3',
1326+
'run_command': code + '\n',
1327+
'parse_argv': True,
1328+
'init_main': False,
1329+
'sys_path_0': '',
1330+
}
1331+
self.check_all_configs("test_init_main", config,
1332+
api=API_PYTHON,
1333+
stderr="Run Python code before Py_InitializeMain")
1334+
13171335
def test_init_parse_argv(self):
13181336
config = {
13191337
'parse_argv': True,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add :c:func:`Py_InitializeMain` function to move to the "Main"
2+
initialization phase, finish the Python initialization. Rename also
3+
:c:member:`!PyConfig._init_main` to :c:member:`PyConfig.init_main`. Patch by
4+
Victor Stinner.

Programs/_freeze_module.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ runtime_init(void)
6262
if (PyInitConfig_SetInt(config, "_install_importlib", 0) < 0) {
6363
goto error;
6464
}
65-
if (PyInitConfig_SetInt(config, "_init_main", 0) < 0) {
65+
if (PyInitConfig_SetInt(config, "init_main", 0) < 0) {
6666
goto error;
6767
}
6868

Programs/_testembed.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,34 @@ static int test_init_run_main(void)
19891989
}
19901990

19911991

1992+
static int test_init_main(void)
1993+
{
1994+
PyConfig config;
1995+
PyConfig_InitPythonConfig(&config);
1996+
1997+
configure_init_main(&config);
1998+
config.init_main = 0;
1999+
init_from_config_clear(&config);
2000+
2001+
/* Use stderr: sys.stdout doesn't exist yet,
2002+
* it is created by Py_InitializeMain() */
2003+
int res = PyRun_SimpleString(
2004+
"import sys; "
2005+
"print('Run Python code before Py_InitializeMain', "
2006+
"file=sys.stderr)");
2007+
if (res < 0) {
2008+
exit(1);
2009+
}
2010+
2011+
PyStatus status = Py_InitializeMain();
2012+
if (PyStatus_Exception(status)) {
2013+
Py_ExitStatusException(status);
2014+
}
2015+
2016+
return Py_RunMain();
2017+
}
2018+
2019+
19922020
static int test_run_main(void)
19932021
{
19942022
PyConfig config;
@@ -2647,6 +2675,7 @@ static struct TestCase TestCases[] = {
26472675
{"test_preinit_parse_argv", test_preinit_parse_argv},
26482676
{"test_preinit_dont_parse_argv", test_preinit_dont_parse_argv},
26492677
{"test_init_run_main", test_init_run_main},
2678+
{"test_init_main", test_init_main},
26502679
{"test_init_sys_add", test_init_sys_add},
26512680
{"test_init_setpath", test_init_setpath},
26522681
{"test_init_setpath_config", test_init_setpath_config},

Python/initconfig.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
190190
// --- Init-only options -----------
191191

192192
SPEC(_config_init, UINT, INIT_ONLY, NO_SYS),
193-
SPEC(_init_main, BOOL, INIT_ONLY, NO_SYS),
193+
SPEC(init_main, BOOL, INIT_ONLY, NO_SYS),
194194
SPEC(_install_importlib, BOOL, INIT_ONLY, NO_SYS),
195195
SPEC(_is_python_build, BOOL, INIT_ONLY, NO_SYS),
196196
SPEC(module_search_paths_set, BOOL, INIT_ONLY, NO_SYS),
@@ -1036,7 +1036,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
10361036
config->_install_importlib = 1;
10371037
config->check_hash_pycs_mode = NULL;
10381038
config->pathconfig_warnings = -1;
1039-
config->_init_main = 1;
1039+
config->init_main = 1;
10401040
#ifdef MS_WINDOWS
10411041
config->legacy_windows_stdio = -1;
10421042
#endif

Python/pylifecycle.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,7 @@ Py_InitializeFromConfig(const PyConfig *config)
14621462
}
14631463
config = _PyInterpreterState_GetConfig(tstate->interp);
14641464

1465-
if (config->_init_main) {
1465+
if (config->init_main) {
14661466
status = pyinit_main(tstate);
14671467
if (_PyStatus_EXCEPTION(status)) {
14681468
return status;
@@ -1508,6 +1508,18 @@ Py_Initialize(void)
15081508
}
15091509

15101510

1511+
PyStatus
1512+
Py_InitializeMain(void)
1513+
{
1514+
PyStatus status = _PyRuntime_Initialize();
1515+
if (_PyStatus_EXCEPTION(status)) {
1516+
return status;
1517+
}
1518+
PyThreadState *tstate = _PyThreadState_GET();
1519+
return pyinit_main(tstate);
1520+
}
1521+
1522+
15111523
static void
15121524
finalize_modules_delete_special(PyThreadState *tstate, int verbose)
15131525
{

0 commit comments

Comments
 (0)