Python - PEP 741 – Python 配置 C API

Python - PEP 741 – Python 配置 C API

摘要

新增 C API,用于配置 Python 初始化,无需依赖 C 结构体,并支持未来 ABI 兼容性扩展。

完善 PEP 587 API,新增 PyInitConfig_AddModule(),可用于添加内建扩展模块(即原“inittab”功能)。

新增 PyConfig_Get()PyConfig_Set() 函数用于获取和设置当前运行时配置。

PEP 587 统一了所有配置 Python 初始化 的方式。本 PEP 进一步统一 Python 预初始化初始化的配置接口。此外,本 PEP仅提供一种嵌入 Python 的方式,不再区分“Python”和“隔离”两种模式(PEP 587 提供了两种),从而简化 API。

底层 PEP 587PyConfig API 仍然保留,适用于需要与 CPython 实现高度耦合的场景(如完整模拟 CPython CLI 行为及其配置机制)。

原理

获取运行时配置

PEP 587 仅能设置初始化配置,不能获取当前运行时配置。

例如,Py_UnbufferedStdioFlag 全局配置变量在 Python 3.12 被弃用,推荐用 PyConfig.buffered_stdio,但只能用于初始化,无法在运行时通过公开 API 获取。

limited C API 用户希望有公开 API 可获取当前配置。

Cython 需要获取 optimization_level 配置项:相关 issue

全局配置变量弃用后,Marc-André Lemburg 要求提供运行时访问这些变量的 C API。

安全修复

为修复 CVE-2020-10735(大整数转字符串拒绝服务),曾讨论向 stable 分支的 PyConfig 结构增加新成员,但这会影响 ABI。

Gregory P. Smith 提出用基于文本的配置文件 API,避免受限于 PyConfig 成员结构:相关讨论

最终决定不在 stable 分支增加新成员,仅在开发分支(3.12)增加 PyConfig.int_max_str_digits,stable 分支用私有全局变量实现。

PyPreConfig 和 PyConfig 冗余

Python 预初始化用 PyPreConfig,初始化用 PyConfig,两者有四个重复成员:dev_modeparse_argvisolateduse_environment

这是因为结构体分离,导致部分 PyConfig 成员在预初始化时就需用到。

嵌入 Python

嵌入 Python 的应用

典型应用:

Linux/FreeBSD/macOS 上,应用通常静态/动态链接 libpython,动态库如 libpython3.12.so

vim 项目可面向 stable ABI,通常用系统 Python,无法选定具体版本,用户希望可动态指定更高版本。

Linux 上也可将 Python 随 Flatpack、AppImage、Snap 等容器一同分发(如 GIMP),即应用自带 Python 版本。

嵌入 Python 的库

典型库:

打包工具生成独立应用

这些工具生成独立应用,不直接链接 libpython。

设置运行时配置

Marc-André Lemburg 要求提供运行时设置配置项的 C API:

  • optimization_level
  • verbose
  • parser_debug
  • inspect
  • write_bytecode

之前可直接设置全局变量:

  • Py_OptimizeFlag
  • Py_VerboseFlag
  • Py_DebugFlag
  • Py_InspectFlag
  • Py_DontWriteBytecodeFlag

这些变量已在 3.12 弃用,计划 3.14 移除。

规范

新增 C API 函数和结构体用于配置 Python 初始化:

  • 创建配置:
    • PyInitConfig 不透明结构
    • 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)

新增运行时获取/设置配置的 C API:

  • PyConfig_Get(name)
  • PyConfig_GetInt(name, &value)
  • PyConfig_Set(name)
  • PyConfig_Names()

API 使用 UTF-8 的 null 结尾字符串表示配置项名。

这些 API 不包含在 limited C API 中。

PyInitConfig 结构体

PyInitConfig 结合了 PyConfig API 的三部分结构,并包含 inittab 成员:

  • PyPreConfig preconfig
  • PyConfig config
  • PyStatus status
  • struct _inittab *inittab(用于 PyInitConfig_AddModule()

PyStatus 不再独立,而并入 PyInitConfig,简化 API。

配置选项

配置项名称与 PyPreConfigPyConfig 成员一致。详见 PyPreConfig 文档PyConfig 文档

弃用/移除配置项不在本 PEP 范围,应具体讨论。

可读写的配置项

下列选项可用 PyConfig_Get() 获取、PyConfig_Set() 设置:

选项类型说明
argvlist[str]sys.argv
base_exec_prefixstrsys.base_exec_prefix
base_executablestrsys._base_executable
base_prefixstrsys.base_prefix
bytes_warningintsys.flags.bytes_warning
exec_prefixstrsys.exec_prefix
executablestrsys.executable
inspectboolsys.flags.inspect (int)
int_max_str_digitsintsys.flags.int_max_str_digits, sys.get_int_max_str_digits(), sys.set_int_max_str_digits()
interactiveboolsys.flags.interactive
module_search_pathslist[str]sys.path
optimization_levelintsys.flags.optimize
parser_debugboolsys.flags.debug (int)
platlibdirstrsys.platlibdir
prefixstrsys.base_prefix
pycache_prefixstrsys.pycache_prefix
quietboolsys.flags.quiet (int)
stdlib_dirstrsys._stdlib_dir
use_environmentboolsys.flags.ignore_environment (int)
verboseintsys.flags.verbose
warnoptionslist[str]sys.warnoptions
write_bytecodeboolsys.flags.dont_write_bytecode (int), sys.dont_write_bytecode (bool)
xoptionsdict[str, str]sys._xoptions

部分选项名与 sys 属性不同,如 optimization_level 对应 sys.flags.optimizePyConfig_Set() 实际设置相关 sys 属性。

xoptionsPyInitConfig 中为字符串列表,每项为 key(值隐含为 True)或 key=value。运行时配置则为字典(key: str -> value: str | True)。

只读配置项

下列选项仅可用 PyConfig_Get() 获取,不可用 PyConfig_Set() 设置:

选项类型说明
allocatorint
buffered_stdiobool
check_hash_pycs_modestr
code_debug_rangesbool
coerce_c_localebool
coerce_c_locale_warnbool
configure_c_stdiobool
configure_localebool
cpu_countintos.cpu_count()
dev_modeboolsys.flags.dev_mode
dump_refsbool
dump_refs_filestr
faulthandlerboolfaulthandler.is_enabled()
filesystem_encodingstrsys.getfilesystemencoding()
filesystem_errorsstrsys.getfilesystemencodeerrors()
hash_seedint
homestr
import_timebool
install_signal_handlersbool
isolatedboolsys.flags.isolated (int)
legacy_windows_fs_encodingbool仅 Windows
legacy_windows_stdiobool仅 Windows
malloc_statsbool
orig_argvlist[str]sys.orig_argv
parse_argvbool
pathconfig_warningsbool
perf_profilingboolsys.is_stack_trampoline_active()
program_namestr
run_commandstr
run_filenamestr
run_modulestr
run_presitestr需 debug 构建
safe_pathbool
show_ref_countbool
site_importboolsys.flags.no_site (int)
skip_source_first_linebool
stdio_encodingstrsys.stdin.encoding
stdio_errorsstrsys.stdin.errors
tracemallocinttracemalloc.is_tracing()
use_frozen_modulesbool
use_hash_seedbool
user_site_directoryboolsys.flags.no_user_site (int)
utf8_modebool
warn_default_encodingbool
_pystatsboolsys._stats_on(), 需 Py_STATS 构建

创建配置

PyInitConfig* PyInitConfig_Create(void)

创建带 Isolated Configuration 默认值的新配置,需用 PyInitConfig_Free() 释放。分配失败返回 NULL

void PyInitConfig_Free(PyInitConfig *config)

释放配置内存。

获取选项

配置项名需为非空、null 结尾 UTF-8 字符串。

int PyInitConfig_HasOption(PyInitConfig *config, const char *name)

若有该项返回 1,否则 0。

int PyInitConfig_GetInt(PyInitConfig *config, const char *name, int64_t *value)

获取整数配置项,成功设 *value 并返回 0,失败设错误信息并返回 -1。

int PyInitConfig_GetStr(PyInitConfig *config, const char *name, char **value)

获取字符串配置项,成功设 *value 并返回 0,失败设错误并返回 -1。需用 free(value) 释放。

int PyInitConfig_GetStrList(PyInitConfig *config, const char *name, size_t *length, char ***items)

获取字符串列表配置项,成功设 *length, *items 并返回 0,失败设错误并返回 -1。需用 PyInitConfig_FreeStrList(length, items) 释放。

void PyInitConfig_FreeStrList(size_t length, char **items)

释放字符串列表内存。

设置选项

配置项名同上。

部分项设置会影响其它项,此逻辑仅在 Py_InitializeFromInitConfig() 内实现,非“Set”函数。

int PyInitConfig_SetInt(PyInitConfig *config, const char *name, int64_t value)

设置整数配置项,成功返回 0,失败设错误并返回 -1。

int PyInitConfig_SetStr(PyInitConfig *config, const char *name, const char *value)

设置字符串配置项(UTF-8),复制字符串,成功返回 0,失败设错误并返回 -1。

int PyInitConfig_SetStrList(PyInitConfig *config, const char *name, size_t length, char * const *items)

设置字符串列表配置项,复制列表,成功返回 0,失败设错误并返回 -1。

int PyInitConfig_AddModule(PyInitConfig *config, const char *name, PyObject* (*initfunc)(void))

添加内建扩展模块到内建模块表。首次 import 时调用 initfunc。多次初始化时,每次都需调用。

类似 PyImport_AppendInittab()

初始化 Python

int Py_InitializeFromInitConfig(PyInitConfig *config)

用配置初始化 Python,成功返回 0,失败设错误或退出码并返回 -1。

PyInitConfig_GetExitcode()

错误处理

int PyInitConfig_GetError(PyInitConfig* config, const char **err_msg)

获取 config 错误信息,若有错误,设 *err_msg 并返回 1,否则设 *err_msg 为 NULL 并返回 0。错误信息为 UTF-8 字符串,调用其它 PyInitConfig 函数前有效,无需手动释放。

int PyInitConfig_GetExitcode(PyInitConfig* config, int *exitcode)

获取 config 退出码,若需退出,设 *exitcode 并返回 1,否则返回 0。仅 Py_InitializeFromInitConfig() 可设退出码(如命令行解析失败或请求显示帮助)。

运行时获取/设置配置

配置项名同上。

PyObject* PyConfig_Get(const char *name)

获取当前运行时配置项值,类型见上表。需持有 GIL,初始化前后不可用。

int PyConfig_GetInt(const char *name, int *value)

同上,获取整型值,成功设 *value 并返回 0,失败抛异常返回 -1。

PyObject* PyConfig_Names(void)

获取所有配置项名,类型为 frozenset。需持有 GIL。

PyObject* PyConfig_Set(const char *name, PyObject *value)

设置当前运行时配置项。若项不存在、值非法、只读或类型不符,均抛 ValueError/TypeError。只读项不可设置。需持有 GIL,初始化前后不可用。

稳定性

各选项及默认值行为每个 Python 版本可能变化,不保证“稳定”。选项可按 PEP 387 流程增删弃用。

与 PyPreConfig 和 PyConfig API 交互

底层 PEP 587 的 PyPreConfig 和 PyConfig API 保留,适用于高度模拟 CPython CLI 行为的场景。

PyPreConfig API 可与本 PEP 初始化 API 联用。已预初始化解释器后,PyInitConfig_SetIntPyConfig_Set 仅允许更新 use_environment,其他预初始化项不可更改,违者报错。

示例

初始化 Python

初始化并设置多种类型配置项,错误返回 -1:

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

    // 设置整数(dev mode)
    if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) {
        goto error;
    }

    // 设置字符串列表(argv)
    char *argv[] = {"my_program", "-c", "pass"};
    if (PyInitConfig_SetStrList(config, "argv", Py_ARRAY_LENGTH(argv), argv) < 0) {
        goto error;
    }

    // 设置字符串(program name)
    if (PyInitConfig_SetStr(config, "program_name", L"my_program") < 0) {
        goto error;
    }

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

error:
    // 显示错误信息
    const char *err_msg;
    (void)PyInitConfig_GetError(config, &err_msg);
    printf("PYTHON INIT ERROR: %s\n", err_msg);
    PyInitConfig_Free(config);

    return -1;
}

增加初始化 bytes_warning 选项

递增初始化配置的 bytes_warning 选项:

int config_bytes_warning(PyInitConfig *config)
{
    int64_t bytes_warning;
    if (PyInitConfig_GetInt(config, "bytes_warning", &bytes_warning)) {
        return -1;
    }
    bytes_warning += 1;
    if (PyInitConfig_SetInt(config, "bytes_warning", bytes_warning)) {
        return -1;
    }
    return 0;
}

获取运行时 verbose 选项

获取当前运行时配置的 verbose 选项:

int get_verbose(void)
{
    int verbose;
    if (PyConfig_GetInt("verbose", &verbose) < 0) {
        // 忽略错误
        PyErr_Clear();
        return -1;
    }
    return verbose;
}

实际中,获取 verbose 基本不会失败,除非未来移除该选项。

实现

向后兼容性

完全向后兼容,仅新增 API,原有如 PyConfig API (PEP 587) 保持不变。

被拒绝方案

文本配置

建议用文本配置兼容 stable ABI 并支持自定义项,如:

bytes_warning = 2
filesystem_encoding = "utf8"
argv = ['python', '-c', 'code']

API 以字符串(非文件)形式传递配置:

void stable_abi_init_demo(int set_path)
{
    PyInit_SetConfig(
        "isolated = 1\n"
        "argv = ['python', '-c', 'code']\n"
        "filesystem_encoding = 'utf-8'\n"
    );
    if (set_path) {
        PyInit_SetConfig("pythonpath = '/my/path'");
    }
}

但这样需要给字符串加引号并转义,字符串数组格式化也不便。直接传递字符串/数组的 API 更简单,也避免了特殊字符(如换行)的歧义。

用整数引用配置项

用字符串做配置项名查找慢,可用整数(类似 type “slot”),如 Py_tp_doc。但定制选项容易冲突,需维护命名空间。且需要大量常量,增加 API 体积。当前配置项仅 62 个,性能非瓶颈,必要时可用哈希查找。热路径下可缓存配置值,大部分项运行时不能变。

多阶段初始化(类似 PEP 432)

Eric Snow 认为初始化应分为 5 个阶段,API 也应体现。PEP 432/587 类似。理想状态下,Py_InitializeFromConfig() 前配置应完整,实际初始化过程中配置会被修改。

但分阶段 API 更复杂,需新增结构和函数,反而加剧复杂性。多结构易致成员重复。本 PEP目标是统一简化现有 API。

本地编码与宽字符串

本地编码字符串和宽字符(wchar_t*)支持被延后,保持 PyInitConfig API 简洁。此类特性主要用于完整模拟 CPython CLI,适合用 PEP 587 的低层 API。

讨论记录

版权

本文档可置于公有领域或 CC0-1.0-Universal 许可下,以更宽松者为准。

原文地址:https://peps.python.org/pep-0741/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdddn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值