首页
/ CPython 初始化配置 C API 实战指南:PyInitConfig、PyConfig 与 PyPreConfig 全解

CPython 初始化配置 C API 实战指南:PyInitConfig、PyConfig 与 PyPreConfig 全解

2026-09-04 18:16:38作者:韦蓉瑛

本文系统梳理 CPython 官方文档 Doc/c-api/init_config.rst 中定义的三套初始化配置接口:面向嵌入场景的新版 PyInitConfig 不透明结构(PEP 741,Python 3.14 引入)、经典的结构体直填式 PyConfig/PyPreConfig(PEP 587,Python 3.8 引入),以及运行期的 PyConfig_Get/PyConfig_Set 查询接口。读完本文,你将掌握从 C 代码创建、修改、初始化 Python 解释器的完整流程,理解每个配置选项的默认值与来源,并能结合源码验证配置解析的底层实现。

三套 API 的定位与版本

CPython 初始化配置 API 经历了三代演进,Doc/c-api/init_config.rst 把它们组织在同一个文档中:

API 引入版本 核心结构 典型用途
PyInitConfig C API 3.14(PEP 741) 不透明指针,键值式读写 嵌入式应用,只关心个别选项
PyConfig C API 3.8(PEP 587) 完整结构体,字段直填 定制化 python 解释器
运行期 PyConfig_Get/PyConfig_Set 3.14 无结构体,按名查询 解释器运行中动态调整

PyInitConfigPyConfig 的关键差异在于:前者是不透明结构(typedef struct PyInitConfig PyInitConfig;,见 initconfig.h 第 289 行),调用方通过字符串选项名读写,无法直接访问字段;后者是完整的 struct PyConfig,所有字段可直填。PyInitConfig 创建后默认使用 Isolated Configuration(隔离配置),适合把 Python 嵌入宿主应用且不希望受环境变量、命令行干扰的场景。

PyInitConfig 完整工作流

创建与释放

PyInitConfig_Create() 通过 calloc 分配结构并用 PyPreConfig_InitIsolatedConfigPyConfig_InitIsolatedConfig 填充默认值(见 initconfig.c 第 3999-4010 行)。分配失败返回 NULL,调用方必须检查。PyInitConfig_Free() 接收 NULL 时不做任何操作,因此可以在多个错误分支中无条件调用。

带错误处理的初始化示例

文档给出了一个"始终启用 Python 开发模式"的完整示例,这是理解错误处理模式的最佳入口:

int init_python(void)
{
    PyInitConfig *config = PyInitConfig_Create();
    if (config == NULL) {
        printf("PYTHON INIT ERROR: memory allocation failed\n");
        return -1;
    }

    // 启用 Python Development Mode
    if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) {
        goto error;
    }

    // 用配置初始化 Python
    if (Py_InitializeFromInitConfig(config) < 0) {
        goto error;
    }
    PyInitConfig_Free(config);
    return 0;

error:
    {
        // 注意:goto 不能跳转到变量声明,所以用大括号包一层
        const char *err_msg;
        (void)PyInitConfig_GetError(config, &err_msg);
        printf("PYTHON INIT ERROR: %s\n", err_msg);
        PyInitConfig_Free(config);
        return -1;
    }
}

这个示例展示了三条错误处理路径:创建失败(内存分配错误)、选项设置失败、初始化失败。goto error 后必须用大括号包裹变量声明,因为 C 语言不允许 goto 目标指向变量声明。

错误与退出码

PyInitConfig_GetError(config, &err_msg) 返回 1 表示有错误,*err_msg 指向一个 UTF-8 字符串;返回 0 表示无错误,*err_msg 被设为 NULL。错误消息在下次对同一 config 调用任何 PyInitConfig 函数之前保持有效,调用方无需释放。

PyInitConfig_GetExitCode(config, &exitcode) 用于区分"错误"与"解释器请求退出"两种状态。退出码只会在 parse_argv 选项非零时由 Py_InitializeFromInitConfig() 设置,典型场景:命令行解析失败(退出码 2)或命令行选项要求显示帮助(退出码 0)。从源码实现看(initconfig.c 第 4028-4068 行),PyInitConfig_GetError 在状态为 EXIT 时会动态生成 "exit code N" 格式的错误消息。

选项读写

所有选项名参数必须是"非 NULL 的空终止 UTF-8 编码字符串"。可用的选项名见下方"配置选项参考"章节。

  • PyInitConfig_HasOption(config, name):返回 1 表示选项存在,0 表示不存在。
  • PyInitConfig_GetInt(config, name, &value):读取整型选项,成功返回 0,失败返回 -1 并在 config 中设置错误。
  • PyInitConfig_GetStr(config, name, &value):读取字符串选项。*value 可能为 NULL(当选项是可选字符串且未设置时)。成功获取的字符串必须用 free(value) 释放(因为内部用 malloc 复制)。
  • PyInitConfig_GetStrList(config, name, &length, &items):读取字符串列表。成功后必须用 PyInitConfig_FreeStrList(length, items) 释放。
  • PyInitConfig_SetInt(config, name, value):设置整型选项,值范围会做边界检查(如 INT_MIN/INT_MAX)。
  • PyInitConfig_SetStr(config, name, value):设置字符串选项,内部会复制字符串,调用方可以安全地释放原字符串。
  • PyInitConfig_SetStrList(config, name, length, items):设置字符串列表,内部会复制整个列表

一个重要的实现细节:设置 hash_seed 选项时,实现会自动把 use_hash_seed 设为 1(见 initconfig.c 第 4323-4325 行);设置 module_search_paths 时会自动把 module_search_paths_set 设为 1(第 4475-4477 行)。这些"副作用"在 Py_InitializeFromInitConfig() 调用时才会完整体现,Set 函数本身不处理所有跨选项联动——例如设置 dev_mode=1 不会自动设置 faulthandler=1,文档明确说明这一逻辑只在初始化阶段执行。

注册内建模块

PyInitConfig_AddModule(config, name, initfunc) 把一个内建扩展模块添加到内建模块表中,类似于 PyImport_AppendInittab。如果 Python 被多次初始化,必须在每次初始化前调用该函数。从源码实现看(initconfig.c 第 4483-4505 行),内部维护一个 _inittab 数组,每次添加都重新分配并追加一个终止项(name=NULL, initfunc=NULL)。

Py_InitializeFromInitConfig 内部流程

Py_InitializeFromInitConfig() 的内部调用链(initconfig.c 第 4509-4534 行):

  1. inittab_size >= 1,调用 PyImport_ExtendInittab() 注册内建模块;
  2. 调用 _PyPreConfig_GetConfig()PyPreConfig 字段同步到 PyConfig
  3. 调用 Py_PreInitializeFromArgs() 完成预初始化(设置内存分配器、locale、UTF-8 模式);
  4. 调用 Py_InitializeFromConfig() 完成正式初始化。

任一步骤返回异常(error 或 exit)时,函数返回 -1 并在 config 中记录状态。

配置选项参考(PyInitConfig 选项表)

PyInitConfig 的所有可用选项在文档中用一张表列出,每个选项映射到 PyConfigPyPreConfig 的一个结构体成员,并标注了类型和可见性。以下是完整继承的选项表:

选项名 对应成员 类型 可见性
"allocator" PyPreConfig.allocator int Read-only
"argv" PyConfig.argv list[str] Public
"base_exec_prefix" PyConfig.base_exec_prefix str Public
"base_executable" PyConfig.base_executable str Public
"base_prefix" PyConfig.base_prefix str Public
"buffered_stdio" PyConfig.buffered_stdio bool Read-only
"bytes_warning" PyConfig.bytes_warning int Public
"check_hash_pycs_mode" PyConfig.check_hash_pycs_mode str Read-only
"code_debug_ranges" PyConfig.code_debug_ranges bool Read-only
"coerce_c_locale" PyPreConfig.coerce_c_locale bool Read-only
"coerce_c_locale_warn" PyPreConfig.coerce_c_locale_warn bool Read-only
"configure_c_stdio" PyConfig.configure_c_stdio bool Read-only
"configure_locale" PyPreConfig.configure_locale bool Read-only
"cpu_count" PyConfig.cpu_count int Public
"dev_mode" PyConfig.dev_mode bool Read-only
"dump_refs" PyConfig.dump_refs bool Read-only
"dump_refs_file" PyConfig.dump_refs_file str Read-only
"exec_prefix" PyConfig.exec_prefix str Public
"executable" PyConfig.executable str Public
"faulthandler" PyConfig.faulthandler bool Read-only
"filesystem_encoding" PyConfig.filesystem_encoding str Read-only
"filesystem_errors" PyConfig.filesystem_errors str Read-only
"hash_seed" PyConfig.hash_seed int Read-only
"home" PyConfig.home str Read-only
"import_time" PyConfig.import_time int Read-only
"inspect" PyConfig.inspect bool Public
"install_signal_handlers" PyConfig.install_signal_handlers bool Read-only
"int_max_str_digits" PyConfig.int_max_str_digits int Public
"interactive" PyConfig.interactive bool Public
"isolated" PyConfig.isolated bool Read-only
"legacy_windows_fs_encoding" PyPreConfig.legacy_windows_fs_encoding bool Read-only
"legacy_windows_stdio" PyPreConfig.legacy_windows_stdio bool Read-only
"malloc_stats" PyConfig.malloc_stats bool Read-only
"module_search_paths" PyConfig.module_search_paths list[str] Public
"optimization_level" PyConfig.optimization_level int Public
"orig_argv" PyConfig.orig_argv list[str] Read-only
"parse_argv" PyConfig.parse_argv bool Read-only
"parser_debug" PyConfig.parser_debug bool Public
"pathconfig_warnings" PyConfig.pathconfig_warnings bool Read-only
"perf_profiling" PyConfig.perf_profiling bool Read-only
"platlibdir" PyConfig.platlibdir str Public
"prefix" PyConfig.prefix str Public
"program_name" PyConfig.program_name str Read-only
"pycache_prefix" PyConfig.pycache_prefix str Public
"quiet" PyConfig.quiet bool Public
"run_command" PyConfig.run_command str Read-only
"run_filename" PyConfig.run_filename str Read-only
"run_module" PyConfig.run_module str Read-only
"run_presite" PyConfig.run_presite str Read-only
"safe_path" PyConfig.safe_path bool Read-only
"show_ref_count" PyConfig.show_ref_count bool Read-only
"site_import" PyConfig.site_import bool Read-only
"skip_source_first_line" PyConfig.skip_source_first_line bool Read-only
"stdio_encoding" PyConfig.stdio_encoding str Read-only
"stdio_errors" PyConfig.stdio_errors str Read-only
"stdlib_dir" PyConfig.stdlib_dir str Public
"tracemalloc" PyConfig.tracemalloc int Read-only
"use_environment" PyConfig.use_environment bool Public
"use_frozen_modules" PyConfig.use_frozen_modules bool Read-only
"use_hash_seed" PyConfig.use_hash_seed bool Read-only
"use_system_logger" PyConfig.use_system_logger bool Read-only
"user_site_directory" PyConfig.user_site_directory bool Read-only
"utf8_mode" PyPreConfig.utf8_mode bool Read-only
"verbose" PyConfig.verbose int Public
"warn_default_encoding" PyConfig.warn_default_encoding bool Read-only
"warnoptions" PyConfig.warnoptions list[str] Public
"write_bytecode" PyConfig.write_bytecode bool Public
"xoptions" PyConfig.xoptions dict[str, str] Public
"_pystats" PyConfig._pystats bool Read-only

可见性含义:

  • Public:可通过 PyConfig_Get 读取,也可通过 PyConfig_Set 设置;
  • Read-only:可通过 PyConfig_Get 读取,但不能通过 PyConfig_Set 设置。

选项名与结构体字段的映射关系由 PYCONFIG_SPEC[]PYPRECONFIG_SPEC[] 两张静态表驱动(initconfig.c 第 118-253 行),使用 offsetof 宏定位字段地址。

运行期配置 API

Python 初始化完成后,可以用 PyConfig_Get/PyConfig_Set 在运行期读写配置选项:

  • PyConfig_Get(name):返回配置选项的当前值作为 Python 对象(bool/int/str/list[str]/dict[str, str]),成功返回新引用,失败设置异常并返回 NULL
  • PyConfig_GetInt(name, &value):以 C int 形式读取,成功返回 0,失败返回 -1 并设置异常。
  • PyConfig_Names():返回所有配置选项名的 frozenset
  • PyConfig_Set(name, value):设置配置选项,对未知名称、非法值、只读选项分别抛出 ValueError,类型不匹配时抛出 TypeError

使用前提:调用线程必须处于"已附加的线程状态"(attached thread state),且在 Python 初始化之后、终结之前。部分选项直接从 sys 模块属性读取(如 "argv" 读自 sys.argv)。PyConfig_Set 还会触发审计事件 cpython.PyConfig_Set

一个值得注意的版本变化:PyConfig_Set 现在会替换 sys.flags(创建新对象),而不是原地修改。

PyConfig 结构体 API(PEP 587)

两种配置模式

PyConfig 支持两种初始化预设:

  • Python ConfigurationPyConfig_InitPythonConfig):构建一个行为等同于常规 python 解释器的定制化解释器。环境变量和命令行参数用于配置 Python,但全局配置变量(如 Py_IgnoreEnvironmentFlag)被忽略。此模式启用 C locale 强制转换(PEP 538)和 UTF-8 模式(PEP 540),取决于 LC_CTYPE locale、PYTHONUTF8PYTHONCOERCECLOCALE 环境变量。

  • Isolated ConfigurationPyConfig_InitIsolatedConfig):把 Python 从系统中隔离,适合嵌入应用。忽略全局配置变量、环境变量、命令行参数(argv 不被解析)和用户 site 目录;C 标准流(如 stdout)和 LC_CTYPE locale 保持不变;不注册信号处理器。

隔离模式示例

文档给出了一个始终运行在隔离模式的完整 main 函数:

int main(int argc, char **argv)
{
    PyStatus status;

    PyConfig config;
    PyConfig_InitPythonConfig(&config);
    config.isolated = 1;

    /* 解码命令行参数,隐式预初始化 Python(隔离模式) */
    status = PyConfig_SetBytesArgv(&config, argc, argv);
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);

    return Py_RunMain();

exception:
    PyConfig_Clear(&config);
    if (PyStatus_IsExit(status)) {
        return status.exitcode;
    }
    /* 显示错误消息并以非零退出码退出 */
    Py_ExitStatusException(status);
}

注意这里先调用 PyConfig_InitPythonConfig 再手动设置 isolated = 1,而不是直接用 PyConfig_InitIsolatedConfig——这是因为需要先解析命令行参数。

PyWideStringList

PyWideStringList 是一个 wchar_t* 字符串列表结构,包含 length(列表长度)和 items(字符串指针数组)两个字段。当 length 非零时,items 必须非 NULL 且所有字符串必须非 NULL

提供两个操作函数(都需要 Python 已预初始化):

  • PyWideStringList_Append(list, item):在末尾追加;
  • PyWideStringList_Insert(list, index, item):在 index 位置插入;若 index 大于等于列表长度则退化为追加;index 必须 >= 0。

PyStatus 状态结构

PyStatus 是初始化函数统一返回的状态类型,可表示三种状态:成功、错误、退出。

typedef struct {
    enum {
        _PyStatus_TYPE_OK=0,
        _PyStatus_TYPE_ERROR=1,
        _PyStatus_TYPE_EXIT=2
    } _type;
    const char *func;      // 产生错误的函数名(可为 NULL)
    const char *err_msg;   // 错误消息
    int exitcode;          // 退出码
} PyStatus;

创建函数:

  • PyStatus_Ok():成功;
  • PyStatus_Error(err_msg):带消息的错误(err_msg 不能为 NULL);
  • PyStatus_NoMemory():内存分配失败;
  • PyStatus_Exit(exitcode):以指定退出码退出。

处理函数:

  • PyStatus_Exception(status):判断是否为错误或退出(为真时必须处理);
  • PyStatus_IsError(status):判断是否为错误;
  • PyStatus_IsExit(status):判断是否为退出;
  • Py_ExitStatusException(status):若为退出则调用 exit(exitcode);若为错误则打印消息并以非零码退出。只能在上一个函数返回非零时调用。

文档附带一个实用的内存分配示例:

PyStatus alloc(void **ptr, size_t size)
{
    *ptr = PyMem_RawMalloc(size);
    if (*ptr == NULL) {
        return PyStatus_NoMemory();
    }
    return PyStatus_Ok();
}

int main(int argc, char **argv)
{
    void *ptr;
    PyStatus status = alloc(&ptr, 16);
    if (PyStatus_Exception(status)) {
        Py_ExitStatusException(status);
    }
    PyMem_Free(ptr);
    return 0;
}

注意:Python 内部使用宏设置 PyStatus.func(记录产生错误的函数名),而公开创建函数把 func 设为 NULL

PyPreConfig 结构体

PyPreConfig 用于预初始化阶段,包含以下字段及默认值:

allocatorint):Python 内存分配器选择。

常量 说明
PYMEM_ALLOCATOR_NOT_SET 0 不更改(使用默认值)
PYMEM_ALLOCATOR_DEFAULT 1 默认内存分配器
PYMEM_ALLOCATOR_DEBUG 2 默认分配器 + 调试钩子
PYMEM_ALLOCATOR_MALLOC 3 使用 C 库 malloc()
PYMEM_ALLOCATOR_MALLOC_DEBUG 4 强制 malloc() + 调试钩子
PYMEM_ALLOCATOR_PYMALLOC 5 Python pymalloc 分配器
PYMEM_ALLOCATOR_PYMALLOC_DEBUG 6 pymalloc + 调试钩子
PYMEM_ALLOCATOR_MIMALLOC 6 mimalloc(文档中编号与 PYMALLOC_DEBUG 重复)
PYMEM_ALLOCATOR_MIMALLOC_DEBUG 7 mimalloc + 调试钩子

PYMEM_ALLOCATOR_PYMALLOC*--without-pymalloc 配置下不可用;PYMEM_ALLOCATOR_MIMALLOC*--without-mimalloc 配置或底层原子支持不可用时不可用。默认值:PYMEM_ALLOCATOR_NOT_SET

configure_localeint):是否把 LC_CTYPE locale 设为用户首选 locale。设为 0 时同时把 coerce_c_localecoerce_c_locale_warn 设为 0。默认:Python 配置为 1,隔离配置为 0。

coerce_c_localeint):值为 2 时强制转换 C locale;值为 1 时读取 LC_CTYPE locale 决定是否转换。默认:Python 配置为 -1,隔离配置为 0。

coerce_c_locale_warnint):非零时,若 C locale 被转换则发出警告。默认:Python 配置为 -1,隔离配置为 0。

dev_modeint):启用 Python 开发模式。默认:Python 模式为 -1,隔离模式为 0。

isolatedint):隔离模式开关。默认:Python 模式为 0,隔离模式为 1。

legacy_windows_fs_encodingint):仅 Windows 可用(#ifdef MS_WINDOWS)。非零时:设 utf8_mode=0filesystem_encoding="mbcs"filesystem_errors="replace"。由 PYTHONLEGACYWINDOWSFSENCODING 环境变量初始化。默认:0。

parse_argvint):非零时,Py_PreInitializeFromArgs/Py_PreInitializeFromBytesArgs 会像常规 Python 一样解析 argv。默认:Python 配置为 1,隔离配置为 0。

use_environmentint):是否使用环境变量。默认:Python 配置为 1,隔离配置为 0。

utf8_modeint):非零时启用 Python UTF-8 模式。由 -X utf8 命令行选项和 PYTHONUTF8 环境变量设为 0 或 1。默认:1。

预初始化流程

预初始化完成三件事:

  1. 设置 Python 内存分配器(allocator);
  2. 配置 LC_CTYPE locale(locale encoding);
  3. 设置 Python UTF-8 模式(utf8_mode)。

当前预配置存储在 _PyRuntime.preconfig 中。提供三个预初始化函数:

  • Py_PreInitialize(&preconfig)
  • Py_PreInitializeFromBytesArgs(&preconfig, argc, argv):若 parse_argv 非零则解析字节字符串 argv
  • Py_PreInitializeFromArgs(&preconfig, argc, argv):若 parse_argv 非零则解析宽字符串 argv

对于 Python Configuration(PyPreConfig_InitPythonConfig),若 Python 带命令行参数初始化,必须把同样的参数传给预初始化,因为它们影响预配置(如编码)。例如 -X utf8 会启用 UTF-8 模式。

预初始化完成后、Py_InitializeFromConfig 之前,可以调用 PyMem_SetAllocator() 安装自定义分配器。若 allocator 设为 PYMEM_ALLOCATOR_NOT_SET,则可以在 Py_PreInitialize 之前调用。

重要约束PyMem_RawMalloc 等 Python 内存分配函数不得在预初始化之前调用(直接调用 C 库 malloc()/free() 始终安全)。Py_DecodeLocale 也不得在预初始化之前调用。

预初始化启用 UTF-8 模式的示例:

PyStatus status;
PyPreConfig preconfig;
PyPreConfig_InitPythonConfig(&preconfig);

preconfig.utf8_mode = 1;

status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status)) {
    Py_ExitStatusException(status);
}

/* 此时 Python 使用 UTF-8 */

Py_Initialize();
/* ... 使用 Python API ... */
Py_Finalize();

PyConfig 结构体字段详解

PyConfig 是包含大部分 Python 配置参数的结构体,完整定义见 initconfig.h 第 133-245 行。使用完毕后必须调用 PyConfig_Clear() 释放内存。

结构体方法

  • PyConfig_InitPythonConfig(&config) / PyConfig_InitIsolatedConfig(&config):用对应预设初始化;
  • PyConfig_SetString(config, &config_str, str):把宽字符字符串复制到目标字段,必要时隐式预初始化;
  • PyConfig_SetBytesString(config, &config_str, str):用 Py_DecodeLocale 解码字节字符串后设置;
  • PyConfig_SetArgv(config, argc, argv) / PyConfig_SetBytesArgv(config, argc, argv):设置命令行参数;
  • PyConfig_SetWideStringList(config, &list, length, items):设置宽字符串列表;
  • PyConfig_Read(&config):一次性读取所有 Python 配置。已初始化的字段保持不变。自 Python 3.11 起,路径配置字段不再在此函数中计算或修改。argv 只被解析一次:解析后 parse_argv 被设为 2,防止二次解析把应用选项误当作 Python 选项;
  • PyConfig_Clear(&config):释放配置内存。

关键约束:大多数 PyConfig 方法会隐式预初始化。预初始化配置基于 PyConfig,因此与 PyPreConfig 共有的字段(dev_modeisolatedparse_argvuse_environment)必须在调用任何 PyConfig 方法之前设置。若使用 PyConfig_SetArgv/PyConfig_SetBytesArgv,该调用必须先于其他方法,因为预初始化配置依赖于命令行参数。

核心字段(按类别组织,完整字段列表见源码注释):

  • 命令行相关argvsys.argv 来源,解析后第一个元素应是脚本文件而非宿主程序)、orig_argvsys.orig_argv)、parse_argvrun_command-c 值)、run_filename(命令行脚本路径)、run_module-m 值)、run_presite(调试构建专用,-X presite)、skip_source_first_line-x 选项)。

  • 路径配置输入homePYTHONHOME)、platlibdirPYTHONPLATLIBDIR,默认 "lib" 或 Windows 的 "DLLs")、pythonpath_envPYTHONPATH)、program_namepathconfig_warnings

  • 路径配置输出module_search_paths/module_search_paths_setsys.path)、stdlib_direxecutablesys.executable)、base_executablesys._base_executable,由 __PYVENV_LAUNCHER__ 设置)、prefix/base_prefixexec_prefix/base_exec_prefix

  • 行为开关isolated(隔离模式)、use_environment(是否读环境变量,-E 设为 0)、safe_path-P 选项,不向 sys.path 前置不安全路径)、site_import-S 设为 0,禁用 site 模块导入)、user_site_directory-s/-I 设为 0)、dev_mode-X dev/PYTHONDEVMODE)、faulthandler-X faulthandler/PYTHONFAULTHANDLER)、tracemalloc-X tracemalloc=N)、perf_profiling-X perf,值为 1 或 2)、install_signal_handlerswrite_bytecode-B/PYTHONDONTWRITEBYTECODE)、verbose-v/PYTHONVERBOSE)、quiet-q)、inspect-i)、interactive-i)、optimization_level-O/PYTHONOPTIMIZE,0/1/2 三级)、parser_debug-d,需调试构建)、int_max_str_digits-X int_max_str_digits,默认 -1 表示 4300)、cpu_count-X cpu_count=N,覆盖 os.cpu_count)、buffered_stdio-u/PYTHONUNBUFFERED)、import_time-X importtime,值为 1 或 2)、code_debug_rangesPYTHONNODEBUGRANGES/-X no_debug_ranges)、show_ref_count-X showrefcount,需 Py_REF_DEBUG)、dump_refsPYTHONDUMPREFS,需 --with-trace-refs 构建)、malloc_statsPYTHONMALLOCSTATS)、hash_seed/use_hash_seedPYTHONHASHSEED)、bytes_warning-b,计划 3.17 移除)、warn_default_encodinglegacy_windows_stdio(仅 Windows)、use_frozen_modulesPYTHON_FROZEN_MODULES,发布构建默认 1)、use_system_logger(仅 macOS/iOS,iOS 默认 1)、pycache_prefix-X pycache_prefix=PATH/PYTHONPYCACHEPREFIX)、check_hash_pycs_mode--check-hash-based-pycs,值 "always"/"never"/"default")、warnoptions-W/PYTHONWARNINGS)、xoptions-X 选项值)、stdio_encoding/stdio_errorsPYTHONIOENCODING)、filesystem_encoding/filesystem_errors_pystats(需 Py_STATS 宏)。

用 PyConfig 初始化解释器

Py_InitializeFromConfig() 接受一个已填充的 PyConfig 结构体。调用方必须用 PyStatus_Exception/Py_ExitStatusException 处理异常。若使用 PyImport_FrozenModules/PyImport_AppendInittab/PyImport_ExtendInittab,必须在预初始化之后、正式初始化之前设置/调用;若 Python 被多次初始化,PyImport_AppendInittab/PyImport_ExtendInittab 必须在每次初始化前调用。

当前配置存储在 PyInterpreterState.config 中。

设置程序名的简洁示例:

void init_python(void)
{
    PyStatus status;

    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* 设置程序名,隐式预初始化 Python */
    status = PyConfig_SetString(&config, &config.program_name,
                                L"/path/to/my_program");
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);
    return;

exception:
    PyConfig_Clear(&config);
    Py_ExitStatusException(status);
}

更完整的示例——修改默认配置、读取配置、覆盖部分参数。注意自 3.11 起,许多参数直到初始化时才计算,因此不能从配置结构体中读取:

PyStatus init_python(const char *program_name)
{
    PyStatus status;

    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* 在读取配置前设置程序名(从 locale 编码解码字节串),
       隐式预初始化 Python */
    status = PyConfig_SetBytesString(&config, &config.program_name,
                                     program_name);
    if (PyStatus_Exception(status)) {
        goto done;
    }

    /* 一次性读取所有配置 */
    status = PyConfig_Read(&config);
    if (PyStatus_Exception(status)) {
        goto done;
    }

    /* 显式指定 sys.path */
    /* 若要修改默认路径集,先完成初始化,
       再用 PySys_GetAttrString("path") */
    config.module_search_paths_set = 1;
    status = PyWideStringList_Append(&config.module_search_paths,
                                     L"/path/to/stdlib");
    if (PyStatus_Exception(status)) {
        goto done;
    }
    status = PyWideStringList_Append(&config.module_search_paths,
                                     L"/path/to/more/modules");
    if (PyStatus_Exception(status)) {
        goto done;
    }

    /* 覆盖 PyConfig_Read() 计算的 executable */
    status = PyConfig_SetString(&config, &config.executable,
                                L"/path/to/my_executable");
    if (PyStatus_Exception(status)) {
        goto done;
    }

    status = Py_InitializeFromConfig(&config);

done:
    PyConfig_Clear(&config);
    return status;
}

路径配置机制

PyConfig 包含多组路径配置字段:

输入homeplatlibdirpathconfig_warningsprogram_namepythonpath_env、当前工作目录(用于取绝对路径)、PATH 环境变量(从 program_name 解析完整路径)、__PYVENV_LAUNCHER__ 环境变量、(仅 Windows)注册表 HKEY_CURRENT_USER/HKEY_LOCAL_MACHINESoftware\Python\PythonCore\X.Y\PythonPath 中的应用路径。

输出base_exec_prefixbase_executablebase_prefixexec_prefixexecutablemodule_search_paths_set/module_search_pathsprefix

若至少一个"输出字段"未设置,Python 会计算路径配置以填充未设置字段。若 module_search_paths_set 为 0,module_search_paths 被覆盖并设 module_search_paths_set 为 1。可以显式设置所有输出字段来完全绕过默认路径计算(字符串非空即视为已设置;module_search_pathsmodule_search_paths_set=1 时视为已设置且不做修改)。把 pathconfig_warnings 设为 0 可抑制路径计算警告(Unix 专用,Windows 本身不产生警告)。base_prefix/base_exec_prefix 未设置时分别继承 prefix/exec_prefix 的值。

配置文件:路径配置使用 pyvenv.cfg._pth 文件(如 python._pth)、pybuilddir.txt(仅 Unix)。若存在 ._pth 文件:设 isolated=1use_environment=0site_import=0user_site_directory=0(3.15 起)、safe_path=1。若 home 未设置且 pyvenv.cfg 存在于 executable 同目录或其父目录,则 prefix/exec_prefix 被设为该位置,base_prefix/base_exec_prefix 保持指向基础安装。3.14 起 prefix/exec_prefix 直接设为 pyvenv.cfg 所在目录(此前由 site 模块完成,因此受 -S 影响)。

Py_RunMain/Py_Main 会修改 sys.path:若 run_filename 是包含 __main__.py 的目录,前置该目录;若 isolated 为 0:run_module 已设置则前置当前目录;run_filename 已设置则前置脚本所在目录;否则前置空字符串(当前工作目录)。site_import 非零时 site 模块可修改 sys.pathuser_site_directory 非零且用户 site 包目录存在时,site 模块追加该目录。

Py_GetArgcArgv()

Py_GetArgcArgv(int *argc, wchar_t ***argv) 获取 Python 修改之前的原始命令行参数,与 PyConfig.orig_argv 对应。

多阶段初始化私有临时 API

这一节介绍 PEP 432 的核心功能——多阶段初始化的私有临时 API:

  • "Core" 阶段(最小 Python):内建类型、内建异常、内建与冻结模块、sys 模块仅部分初始化(如 sys.path 尚不存在);
  • "Main" 阶段(完全初始化):安装并配置 importlib、应用路径配置、安装信号处理器、完成 sys 模块初始化(创建 sys.stdoutsys.path)、启用 faulthandler/tracemalloc 等可选功能、导入 site 模块。

私有临时 API:

  • PyConfig._init_main:设为 0 时 Py_InitializeFromConfig 在 "Core" 阶段停止;
  • _Py_InitializeMain():进入 "Main" 阶段,完成 Python 初始化。

"Core" 阶段不导入任何模块,importlib 未配置,路径配置仅在 "Main" 阶段应用。这允许在 Python 中自定义路径配置、安装自定义 sys.meta_path 导入器或导入钩子。此 API 被标记为私有且临时,可能在正式公开 API 设计完成前随时修改或移除。

在 "Core" 与 "Main" 之间运行 Python 代码的示例:

void init_python(void)
{
    PyStatus status;

    PyConfig config;
    PyConfig_InitPythonConfig(&config);
    config._init_main = 0;

    /* ... 自定义 config 配置 ... */

    status = Py_InitializeFromConfig(&config);
    PyConfig_Clear(&config);
    if (PyStatus_Exception(status)) {
        Py_ExitStatusException(status);
    }

    /* 用 sys.stderr,因为 sys.stdout 只在
       _Py_InitializeMain() 中创建 */
    int res = PyRun_SimpleString(
        "import sys; "
        "print('Run Python code before _Py_InitializeMain', "
               "file=sys.stderr)");
    if (res < 0) {
        exit(1);
    }

    /* ... 此处放更多配置代码 ... */

    status = _Py_InitializeMain();
    if (PyStatus_Exception(status)) {
        Py_ExitStatusException(status);
    }
}

测试用例验证

仓库中的嵌入测试程序 Programs/_testembed.c 包含 test_initconfig_api() 函数(第 1776-1835 行),演示了 PyInitConfig 的完整使用:创建配置、设置 configure_locale/dev_mode/hash_seed/perf_profiling/program_name/pycache_prefix/xoptions 等选项、调用 Py_InitializeFromInitConfig、释放配置,以及在错误分支中获取错误消息。test_initconfig_get_api()(第 1838 行起)进一步验证了 PyInitConfig_HasOption/GetInt/GetStr/GetStrList/SetInt/SetStr/SetStrList 的读写一致性,并验证了 hash_seed 设置后 use_hash_seed 自动变为 1 的联动行为。

源码索引

登录后查看全文
热门项目推荐
相关项目推荐