Add a C API to configure the Python initialization without relying on Cstructures and the ability to make ABI-compatible changes in the future.
CompletePEP 587 API by addingPyInitConfig_AddModule() which can beused to add a built-in extension module; feature previously referred toas the “inittab”.
AddPyConfig_Get() andPyConfig_Set() functions toget and set the current runtime configuration.
PEP 587 “Python Initialization Configuration” unified all the ways toconfigure the Pythoninitialization. This PEP unifies also theconfiguration of the Pythonpreinitialization and the Pythoninitialization in a single API. Moreover, this PEP only provides asingle choice to embed Python, instead of having two “Python” and“Isolated” choices (PEP 587), to simplify the API further.
The lower levelPEP 587PyConfig API remains available for usecases with an intentionally higher level of coupling to CPythonimplementation details (such as emulating the full functionality ofCPython’s CLI, including its configuration mechanisms).
PEP 587 has no API toget thecurrent runtime configuration,only toconfigure the Pythoninitialization.
For example, the global configuration variablePy_UnbufferedStdioFlag was deprecated in Python 3.12 and usingPyConfig.buffered_stdio is recommended instead. It only works toconfigure Python, there is no public API to getPyConfig.buffered_stdio.
Users of the limited C API are asking for a public API to get thecurrent runtime configuration.
Cython needs to get theoptimization_level configuration option:issue.
When global configuration variables were deprecated in 2022,Marc-AndréLemburg requesteda C API to access these configuration variables at runtime (not onlyduring Python initialization).
To fixCVE-2020-10735,a denial-of-service when converting a very large string to an integer (in base10), it was discussed to add a newPyConfig member to stablebranches which affects the ABI.
Gregory P. Smith proposed a different API using text based configurationfile to not be limited byPyConfig members:FR: Allow privateruntime config to enable extending without breaking the PyConfig ABI(August 2022).
In the end, it was decided to not add a newPyConfig member tostable branches, but only add a newPyConfig.int_max_str_digitsmember to the development branch (which became Python 3.12). A dedicatedprivate global variable (unrelated toPyConfig) is used in stablebranches.
The Python preinitialization uses thePyPreConfig structure and thePython initialization uses thePyConfig structure. Both structureshave four duplicated members:dev_mode,parse_argv,isolatedanduse_environment.
The redundancy is caused by the fact that the two structures areseparated, whereas somePyConfig members are needed by thepreinitialization.
Examples:
On Linux, FreeBSD and macOS, applications are usually either staticallylinked to alibpython, or load dynamically alibpython . Thelibpython shared library is versioned, example:libpython3.12.so for Python 3.12 on Linux.
The vim project can target the stable ABI. Usually, the “system Python”version is used. It’s not currently possible to select which Pythonversion to use. Users would like the ability to select a newer Pythonon demand.
On Linux, another approach to deploy an application embedding Python,such as GIMP, is to include Python in Flatpack, AppImage or Snap“container”. In this case, the application brings its own copy of Pythonversion with the container.
Examples:
These utilities create standalone applications, they are not linked tolibpython.
Marc-André Lemburg requesteda C API toset the value of some configuration options at runtime:
optimization_levelverboseparser_debuginspectwrite_bytecodePreviously, it was possible to set directly global configurationvariables:
Py_OptimizeFlagPy_VerboseFlagPy_DebugFlagPy_InspectFlagPy_DontWriteBytecodeFlagBut these configuration flags were deprecated in Python 3.12 and arescheduled for removal in Python 3.14.
Add C API functions and structure to configure the Pythoninitialization:
PyInitConfig opaque structure.PyInitConfig_Create().PyInitConfig_Free(config).PyInitConfig_HasOption(config,name).PyInitConfig_GetInt(config,name,&value).PyInitConfig_GetStr(config,name,&value).PyInitConfig_GetStrList(config,name,&length,&items).PyInitConfig_FreeStrList().PyInitConfig_SetInt(config,name,value).PyInitConfig_SetStr(config,name,value).PyInitConfig_SetStrList(config,name,length,items).PyInitConfig_AddModule(config,name,initfunc)Py_InitializeFromInitConfig(config).PyInitConfig_GetError(config,&err_msg).PyInitConfig_GetExitcode(config,&exitcode).Add C API functions to get and set the current runtime configuration:
PyConfig_Get(name).PyConfig_GetInt(name,&value).PyConfig_Set(name).PyConfig_Names().The C API uses null-terminated UTF-8 encoded strings to refer to aconfiguration option name.
These C API functions are excluded from the limited C API.
ThePyInitConfig structure is implemented by combining the threestructures of thePyConfig API and has aninittab member aswell:
PyPreConfigpreconfigPyConfigconfigPyStatusstatusstruct_inittab*inittab forPyInitConfig_AddModule()ThePyStatus status is no longer separated, but part of the unifiedPyInitConfig structure, which makes the API easier to use.
Configuration options are named afterPyPreConfig andPyConfigstructure members. See thePyPreConfig documentation andthePyConfig documentation.
Deprecating and removing configuration options is out of the scope ofthe PEP and should be discussed on a case by case basis.
Following options can be get byPyConfig_Get() and set andPyConfig_Set().
| Option | Type | Comment |
|---|---|---|
argv | list[str] | API:sys.argv. |
base_exec_prefix | str | API:sys.base_exec_prefix. |
base_executable | str | API:sys._base_executable. |
base_prefix | str | API:sys.base_prefix. |
bytes_warning | int | API:sys.flags.bytes_warning. |
exec_prefix | str | API:sys.exec_prefix. |
executable | str | API:sys.executable. |
inspect | bool | API:sys.flags.inspect (int). |
int_max_str_digits | int | API:sys.flags.int_max_str_digits,sys.get_int_max_str_digits() andsys.set_int_max_str_digits(). |
interactive | bool | API:sys.flags.interactive. |
module_search_paths | list[str] | API:sys.path. |
optimization_level | int | API:sys.flags.optimize. |
parser_debug | bool | API:sys.flags.debug (int). |
platlibdir | str | API:sys.platlibdir. |
prefix | str | API:sys.base_prefix. |
pycache_prefix | str | API:sys.pycache_prefix. |
quiet | bool | API:sys.flags.quiet (int). |
stdlib_dir | str | API:sys._stdlib_dir. |
use_environment | bool | API:sys.flags.ignore_environment (int). |
verbose | int | API:sys.flags.verbose. |
warnoptions | list[str] | API:sys.warnoptions. |
write_bytecode | bool | API:sys.flags.dont_write_bytecode (int) andsys.dont_write_bytecode (bool). |
xoptions | dict[str,str] | API:sys._xoptions. |
Some option names are different thansys attributes, such asoptimization_level option andsys.flags.optimize attribute.PyConfig_Set() sets the correspondingsys attribute.
Thexoptions is a list of strings inPyInitConfig where eachstring has the formatkey (value isTrue implicitly) orkey=value. In the current runtime configuration, it becomes adictionary (key:str →value:str|True).
Following options can be get byPyConfig_Get(), but cannot be set byPyConfig_Set().
| Option | Type | Comment |
|---|---|---|
allocator | int | |
buffered_stdio | bool | |
check_hash_pycs_mode | str | |
code_debug_ranges | bool | |
coerce_c_locale | bool | |
coerce_c_locale_warn | bool | |
configure_c_stdio | bool | |
configure_locale | bool | |
cpu_count | int | API:os.cpu_count() (int|None). |
dev_mode | bool | API:sys.flags.dev_mode. |
dump_refs | bool | |
dump_refs_file | str | |
faulthandler | bool | API:faulthandler.is_enabled(). |
filesystem_encoding | str | API:sys.getfilesystemencoding(). |
filesystem_errors | str | API:sys.getfilesystemencodeerrors(). |
hash_seed | int | |
home | str | |
import_time | bool | |
install_signal_handlers | bool | |
isolated | bool | API:sys.flags.isolated (int). |
legacy_windows_fs_encoding | bool | Windows only. |
legacy_windows_stdio | bool | Windows only. |
malloc_stats | bool | |
orig_argv | list[str] | API:sys.orig_argv. |
parse_argv | bool | |
pathconfig_warnings | bool | |
perf_profiling | bool | API:sys.is_stack_trampoline_active(). |
program_name | str | |
run_command | str | |
run_filename | str | |
run_module | str | |
run_presite | str | need a debug build. |
safe_path | bool | |
show_ref_count | bool | |
site_import | bool | API:sys.flags.no_site (int). |
skip_source_first_line | bool | |
stdio_encoding | str | API:sys.stdin.encoding,sys.stdout.encoding andsys.stderr.encoding. |
stdio_errors | str | API:sys.stdin.errors,sys.stdout.errors andsys.stderr.errors. |
tracemalloc | int | API:tracemalloc.is_tracing() (bool). |
use_frozen_modules | bool | |
use_hash_seed | bool | |
user_site_directory | bool | API:sys.flags.no_user_site (int). |
utf8_mode | bool | |
warn_default_encoding | bool | |
_pystats | bool | API:sys._stats_on(),sys._stats_off().Need aPy_STATS build. |
PyInitConfig structure:PyInitConfig*PyInitConfig_Create(void):It must be freed withPyInitConfig_Free().
ReturnNULL on memory allocation failure.
voidPyInitConfig_Free(PyInitConfig*config):The configuration optionname parameter must be a non-NULLnull-terminated UTF-8 encoded string.
intPyInitConfig_HasOption(PyInitConfig*config,constchar*name):Return1 if the option exists, or return0 otherwise.
intPyInitConfig_GetInt(PyInitConfig*config,constchar*name,int64_t*value):0 on success.-1 on error.intPyInitConfig_GetStr(PyInitConfig*config,constchar*name,char**value):0 on success.-1 on error.On success, the string must be released withfree(value).
intPyInitConfig_GetStrList(PyInitConfig*config,constchar*name,size_t*length,char***items):0 on success.-1 on error.On success, the string list must be released withPyInitConfig_FreeStrList(length,items).
voidPyInitConfig_FreeStrList(size_tlength,char**items):PyInitConfig_GetStrList().The configuration optionname parameter must be a non-NULLnull-terminated UTF-8 encoded string.
Some configuration options have side effects on other options. Thislogic is only implemented whenPy_InitializeFromInitConfig() iscalled, not by the “Set” functions below. For example, settingdev_mode to1 does not setfaulthandler to1.
intPyInitConfig_SetInt(PyInitConfig*config,constchar*name,int64_tvalue):0 on success.-1 on error.intPyInitConfig_SetStr(PyInitConfig*config,constchar*name,constchar*value):0 on success.-1 on error.intPyInitConfig_SetStrList(PyInitConfig*config,constchar*name,size_tlength,char*const*items):0 on success.-1 on error.intPyInitConfig_AddModule(PyInitConfig*config,constchar*name,PyObject*(*initfunc)(void)):The new module can be imported by the namename, and uses thefunctioninitfunc as the initialization function called on thefirst attempted import.
0 on success.-1 on error.If Python is initialized multiple times,PyInitConfig_AddModule() must be called at each Pythoninitialization.
Similar to thePyImport_AppendInittab() function.
intPy_InitializeFromInitConfig(PyInitConfig*config):0 on success.-1 on error.-1 if Python wants toexit.SeePyInitConfig_GetExitcode() for the exitcode case.
intPyInitConfig_GetError(PyInitConfig*config,constchar**err_msg):1 if an error is set.NULL and return0 otherwise.An error message is an UTF-8 encoded string.
Ifconfig has an exit code, format the exit code as an errormessage.
The error message remains valid until anotherPyInitConfigfunction is called withconfig. The caller doesn’t have to free theerror message.
intPyInitConfig_GetExitcode(PyInitConfig*config,int*exitcode):1 if Python wants to exit.0 ifconfig has no exit code set.Only thePy_InitializeFromInitConfig() function can set an exitcode if theparse_argv option is non-zero.
An exit code can be set when parsing the command line failed (exitcode 2) or when a command line option asks to display the commandline help (exit code 0).
The configuration optionname parameter must be a non-NULLnull-terminated UTF-8 encoded string.
PyObject*PyConfig_Get(constchar*name):NULL on error.The object type depends on the option: seeConfiguration Optionstables.
Other options are get from internalPyPreConfig andPyConfig structures.
The caller must hold the GIL. The function cannot be called beforePython initialization nor after Python finalization.
intPyConfig_GetInt(constchar*name,int*value):PyConfig_Get(), but get the value as an integer.*value and return0 success.-1 on error.PyObject*PyConfig_Names(void):frozenset.Set an exception and returnNULL on error.
The caller must hold the GIL.
PyObject*PyConfig_Set(constchar*name,PyObject*value):ValueError if there is no optionname.ValueError ifvalue is an invalid value.ValueError if the option is read-only: cannot be set.TypeError ifvalue has not the proper type.Read-only configuration options cannot be set.
The caller must hold the GIL. The function cannot be called beforePython initialization nor after Python finalization.
The behavior of options, the default option values, and the Pythonbehavior can change at each Python version: they are not “stable”.
Moreover, configuration options can be added, deprecated and removedfollowing the usualPEP 387 deprecation process.
The lower levelPEP 587PyPreConfig andPyConfig APIs remainavailable and fully supported. As noted in the Abstract, they remain thepreferred approach for embedding use cases that are aiming to closelyemulate the behaviour of the full CPython CLI, rather than just making aPython runtime available as part of a larger application.
ThePyPreConfig APIs may be used in combination with theinitialization API in this PEP. In such cases, the read-only vsread/write restrictions for preconfiguration settings apply toPyInitConfig_SetInt in addition toPyConfig_Set once theinterpreter has been preconfigured (specifically, onlyuse_environment may be updated, attempting to update any of theother preconfiguration variables will report an error).
Example initializing Python, set configuration options of various types,return-1 on error:
intinit_python(void){PyInitConfig*config=PyInitConfig_Create();if(config==NULL){printf("PYTHON INIT ERROR: memory allocation failed\n");return-1;}// Set an integer (dev mode)if(PyInitConfig_SetInt(config,"dev_mode",1)<0){gotoerror;}// Set a list of UTF-8 strings (argv)char*argv[]={"my_program","-c","pass"};if(PyInitConfig_SetStrList(config,"argv",Py_ARRAY_LENGTH(argv),argv)<0){gotoerror;}// Set a UTF-8 string (program name)if(PyInitConfig_SetStr(config,"program_name",L"my_program")<0){gotoerror;}// Initialize Python with the configurationif(Py_InitializeFromInitConfig(config)<0){gotoerror;}PyInitConfig_Free(config);return0;error:// Display the error messageconstchar*err_msg;(void)PyInitConfig_GetError(config,&err_msg);printf("PYTHON INIT ERROR: %s\n",err_msg);PyInitConfig_Free(config);return-1;}
Example increasing thebytes_warning option of an initializationconfiguration:
intconfig_bytes_warning(PyInitConfig*config){int64_tbytes_warning;if(PyInitConfig_GetInt(config,"bytes_warning",&bytes_warning)){return-1;}bytes_warning+=1;if(PyInitConfig_SetInt(config,"bytes_warning",bytes_warning)){return-1;}return0;}
Example getting the current runtime value of the configuration optionverbose:
intget_verbose(void){intverbose;if(PyConfig_GetInt("verbose",&verbose)<0){// Silently ignore the errorPyErr_Clear();return-1;}returnverbose;}
On error, the function silently ignores the error and returns-1. Inpractice, getting theverbose option cannot fail, unless a futurePython version removes the option.
Changes are fully backward compatible. Only new APIs are added.
Existing API such as thePyConfig C API (PEP 587) are leftunchanged.
It was proposed to provide the configuration as text to make the APIcompatible with the stable ABI and to allow custom options.
Example:
# integerbytes_warning=2# stringfilesystem_encoding="utf8"# comment# list of stringsargv=['python','-c','code']
The API would take the configuration as a string, not as a file. Examplewith a hypotheticalPyInit_SetConfig() function:
voidstable_abi_init_demo(intset_path){PyInit_SetConfig("isolated = 1\n""argv = ['python', '-c', 'code']\n""filesystem_encoding = 'utf-8'\n");if(set_path){PyInit_SetConfig("pythonpath = '/my/path'");}}
The example ignores error handling to make it easier to read.
The problem is that generating such configuration text requires addingquotes to strings and to escape quotes in strings. Formatting an arrayof strings becomes non-trivial.
Providing an API to format a string or an array of strings is not reallyworth it, whereas Python can provide directly an API to set aconfiguration option where the value is passed directly as a string oran array of strings. It avoids giving special meaning to somecharacters, such as newline characters, which would have to be escaped.
Using strings to refer to a configuration option requires comparingstrings which can be slower than comparing integers.
Use integers, similar to type “slots” such asPy_tp_doc, to refer toa configuration option. Theconstchar*name parameter is replacedwithintoption.
Accepting custom options is more likely to cause conflicts when usingintegers, since it’s harder to maintain “namespaces” (ranges) forinteger options. Using strings, a simple prefix with a colon separatorcan be used.
Integers also requires maintaining a list of integer constants and somake the C API and the Python API larger.
Python 3.13 only has around 62 configuration options, and so performanceis not really a blocker issue. If better performance is needed later, ahash table can be used to get an option by its name.
If getting a configuration option is used in hot code, the value can beread once and cached. By the way, most configuration options cannot bechanged at runtime.
Eric Snow expressed concernsthat this proposal might reinforce with embedders the idea thatinitialization is a single monolithic step. He argued that initializationinvolves 5 distinct phases and even suggested that the API shouldreflect this explicitly. Eric proposed that, at the very least, theimplementation of initialization should reflect the phases, in partfor improved code health. Overall, his explanation has somesimilarities withPEP 432 andPEP 587.
Another of Eric’s key points relevant to this PEP was that, ideally,the config passed toPy_InitializeFromConfig() should be completebefore that function is called, whereas currently initializationactually modifies the config.
While Eric wasn’t necessarily suggesting an alternative to PEP 741,any proposal to add a granular initialization API around phases iseffectively the opposite of what this PEP is trying to accomplish.Such API is more complicated, it requires adding new public structuresand new public functions. It makes the Python initialization morecomplicated, rather than this PEP tries to unify existing APIs and makethem simpler (the opposite). Having multiple structures for similarpurpose can lead to duplicate members, similar issue than duplicatedmembers between existingPyPreConfig andPyConfig structures.
Accepting strings encoded to the locale encoding and accepting widestrings (wchar_t*) in thePyInitConfig API was deferred to keepthePyInitConfig API simple and avoid the complexity of the Pythonpreinitialization. These features are also mostly needed when emulatingthe full CPython CLI behaviour, and hence better served by the lowerlevelPEP 587 API.
This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0741.rst
Last modified:2024-09-03 13:37:25 GMT