CPython C API 字符串转换与格式化:PyOS_snprintf、PyOS_strtoul 与 PyOS_double_to_string 深入解析

CPython C API 字符串转换与格式化:PyOS_snprintf、PyOS_strtoul 与 PyOS_double_to_string 深入解析 CPython C API 字符串转换与格式化PyOS_snprintf、PyOS_strtoul 与 PyOS_double_to_string 深入解析【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇基于 CPython 官方文档 Doc/c-api/conversion.rst 展开系统讲解 C 扩展开发中用于数字转换与格式化字符串输出的一组 C APIPyOS_snprintf/PyOS_vsnprintf、PyOS_strtoul/PyOS_strtol、PyOS_string_to_double/PyOS_double_to_string、大小写无关字符串比较函数以及 locale 无关的字符分类宏。读完本文你将掌握这些 API 的完整参数语义、返回值约定与边界行为并能结合源码如 Python/mysnprintf.c、Python/pystrtod.c理解其跨平台一致性是如何保证的从而在 C 扩展中安全地完成字符串与数字的互相转换。一、PyOS_snprintf / PyOS_vsnprintf跨平台一致的格式化输出1.1 接口声明与用途两个函数声明位于公共头文件 Include/pyerrors.hPyAPI_FUNC(int) PyOS_snprintf(char *str, size_t size, const char *format, ...) Py_GCC_ATTRIBUTE((format(printf, 3, 4))); PyAPI_FUNC(int) PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va) Py_GCC_ATTRIBUTE((format(printf, 3, 0)));PyOS_snprintf(char *str, size_t size, const char *format, ...)根据格式字符串format及后续可变参数向str输出不超过size字节的内容语义参照 Unix 手册页snprintf(3)。PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)同上但参数来自va_list va参照vsnprintf(3)。它们本质上是对标准 C 库snprintf/vsnprintf的包装。包装的目的不是功能扩展而是在标准 C 函数没有保证的角落情形corner cases中提供一致的行为——因为不同平台尤其是 MSVC 的_vsnprintf在缓冲区不足时的返回值和填充行为并不相同。1.2 三条硬性保证文档与实现注释给出的保证完全一致返回时str[size-1]必定为\0永不向str写入超过size字节含结尾\0调用约束str ! NULL、size 0、format ! NULL、size INT_MAX。从源码实现看Python/mysnprintf.c 中PyOS_vsnprintf的逻辑非常短先assert检查约束对size INT_MAX - 1直接返回负值-666然后调用平台的vsnprintfMSVC 下为_vsnprintf最后无条件执行str[size-1] \0。PyOS_snprintf则只是va_start/va_end后转发给PyOS_vsnprintf。一个重要的实用推论是由于要求str ! NULL且size 0C99 中那种n snprintf(NULL, 0, ...)用来计算所需缓冲区大小的惯用法在这里没有等价物。如果需要探测长度应自行分配临时缓冲区或用其他手段估算。1.3 返回值的三种情形返回值rv的解释规则如下情形含义0 rv size转换成功共写入rv个字符不含str[rv]处的结尾\0rv size输出被截断若不截断成功则需要rv 1字节的缓冲区。此时str[size-1]为\0rv 0转换失败str[size-1]仍为\0但str其余内容未定义失败原因取决于底层平台格式码错误、非 C99 平台截断、或无vsnprintf时临时缓冲区分配失败等源码注释Python/mysnprintf.c还补充了rv 0的可能成因libc 检测到格式码错误、平台上vsnprintf语义非 C99 导致截断、或平台根本没有vsnprintf且PyMem_Malloc申请临时缓冲区失败。二、PyOS_strtoul / PyOS_strtollocale 无关的整数字符串转换2.1 PyOS_strtoulunsigned long PyOS_strtoul(const char *str, char **ptr, int base); /* 3.2 新增 */将str的初始部分按进制base转换为unsigned long值语义参照 Unix 手册页strtoul(3)base必须在2到36之间含或为特殊值0前导空白被忽略字母大小写不敏感base为0时通过前缀0b、0o、0x自动判断二进制/八进制/十六进制没有这些前缀时默认按十进制10处理若ptr非NULL转换结束后*ptr指向扫描结束位置之后的字符溢出时设置errno为ERANGE返回ULONG_MAX无法执行任何转换时返回0。2.2 PyOS_strtollong PyOS_strtol(const char *str, char **ptr, int base); /* 3.2 新增 */与PyOS_strtoul行为相同只是返回long溢出时返回LONG_MAX。两个函数都声明在公共头文件 Include/longobject.h实现在 Python/mystrtoul.c。该实现文件开头的注释说明了动机这是“strtol()和strtoul()的改名副本用于避免命名冲突”因此是locale 无关的——不受setlocale影响行为在所有平台上一致。PyOS_strtol的实现Python/mystrtoul.c 起是先调用PyOS_strtoul再处理符号与溢出源码注释中也坦承strtol的溢出检查较繁琐。三、PyOS_string_to_double / PyOS_double_to_string浮点数与字符串互转这对函数声明在公共头文件 Include/pystrtod.h实现在 Python/pystrtod.c字符串转 double与 Python/dtoa.cdouble 转字符串。3.1 PyOS_string_to_double3.1 新增double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception);把字符串s转换为double失败时抛出 Python 异常。它接受的字符串集合与 Python 内置float()构造函数接受的集合一致唯一区别是s不得含有前导或尾随空白。转换与当前 locale 无关。行为细则endptr为NULL转换整个字符串。若字符串不是合法的浮点数表示抛出ValueError并返回-1.0。endptr非NULL尽可能多地转换并令*endptr指向第一个未被转换的字符若字符串的初始段就不是合法的浮点数表示则*endptr指向字符串开头抛出ValueError并返回-1.0。溢出如1e500在很多平台都超出 double 范围若overflow_exception为NULL返回带相应符号的INFINITY且不设置任何异常否则overflow_exception必须指向一个 Python 异常对象函数抛出该异常并返回-1.0。两种情况下*endptr都指向转换值之后的第一个字符。其他错误如内存不足设置相应的 Python 异常并返回-1.0。C 扩展中的典型用法可以参见 Modules/_pickle.cd PyOS_string_to_double(s, endptr, PyExc_OverflowError);即溢出时抛出OverflowError这对应 Python 层面float(1e500)的行为。该函数还有专门的 C API 测试覆盖见 Modules/_testcapi/float.c 中对PyOS_string_to_double的多组断言测试。3.2 PyOS_double_to_string3.1 新增char *PyOS_double_to_string(double val, char format_code, int precision, int flags, int *ptype);按给定的format_code、precision和flags将double值val转换为字符串format_code必须是e、E、f、F、g、G或r之一。其中r表示标准的repr()格式此时precision必须为 0会被忽略。flags是下列常量的按位或组合定义见 Include/pystrtod.h宏值作用Py_DTSF_SIGN0x01即使val非负也在返回字符串前强制加上符号字符Py_DTSF_ADD_DOT_00x02确保返回字符串“看起来不像整数”结果若是整数则追加.0Py_DTSF_ALT0x04应用“alternate”格式化规则具体含义依赖format_code对应PyOS_snprintf文档中#标志说明符的行为Py_DTSF_NO_NEG_00x08负零被转换为正零3.11 新增ptype若非NULL会按val的类型被设置为下列常量之一Include/pystrtod.h*ptype含义Py_DTST_FINITE有限数Py_DTST_INFINITE无穷数Py_DTST_NAN非数字NaN返回值指向转换结果字符串的缓冲区转换失败时返回NULL。调用者负责通过PyMem_Free释放返回的字符串头文件注释中同样有这一提醒。一个真实用例pickle模块序列化浮点数时Modules/_pickle.cbuf PyOS_double_to_string(x, r, 0, Py_DTSF_ADD_DOT_0, NULL);即用repr格式输出、并保证整数值的浮点数带.0后缀——这正是 Python 中repr(1.0)输出1.0而非1的底层保证之一。此外PyOS_string_to_double、PyOS_double_to_string、PyOS_snprintf、PyOS_vsnprintf均属于 CPython 的稳定 ABIStable ABI可从 Misc/stable_abi.toml 及测试 Lib/test/test_stable_abi_ctypes.py 得到印证这意味着即使使用Py_LIMITED_API的扩展也能直接调用它们。四、大小写无关的字符串比较4.1 PyOS_mystricmp / PyOS_mystrnicmpint PyOS_mystricmp(const char *str1, const char *str2); int PyOS_mystrnicmp(const char *str1, const char *str2, Py_ssize_t size);大小写无关的字符串比较行为几乎等同于strcmp/strncmp区别在于只对 ASCII 字符忽略大小写字符串相等返回0str1字典序在str2之前返回负值之后返回正值参数中 NUL 字节表示字符串结束对PyOS_mystrnicmpsize给出字符串的最大长度等价于在size索引处存在 NUL这两个函数不使用 locale因此跨平台行为一致。4.2 PyOS_stricmp / PyOS_strnicmpint PyOS_stricmp(const char *str1, const char *str2); int PyOS_strnicmp(const char *str1, const char *str2, Py_ssize_t size);在 Windows 上它们分别是_stricmp与_strnicmp的别名在其他平台上它们是PyOS_mystricmp与PyOS_mystrnicmp的别名。这一别名关系直接定义在公共头文件 Include/pystrcmp.h 中#define PyOS_strnicmp PyOS_mystrnicmp #define PyOS_stricmp PyOS_mystricmp_mystricmp/_mystrnicmp的实现在 Python/pystrcmp.c。C API 测试 Modules/_testlimitedcapi/pyos.c 验证了边界行为例如PyOS_mystrnicmp(, , 0) 0、PyOS_mystrnicmp(insert, ins, 3) 0size恰好截断到前缀时视为相等。五、locale 无关的字符分类与转换宏以下宏提供不依赖 locale的字符分类与大小写转换这一点与标准库ctype.h其行为受当前 locale 影响不同。参数必须是有符号或无符号的char。宏行为Py_ISALNUM(c)c是字母数字字符时返回真Py_ISALPHA(c)c是英文字母a-z、A-Z时返回真Py_ISDIGIT(c)c是十进制数字0-9时返回真Py_ISLOWER(c)c是小写 ASCII 字母a-z时返回真Py_ISUPPER(c)c是大写 ASCII 字母A-Z时返回真Py_ISSPACE(c)c是空白字符空格、制表符、回车、换行、垂直制表符或换页符时返回真Py_ISXDIGIT(c)c是十六进制数字0-9、a-f、A-F时返回真Py_TOLOWER(c)返回c的小写等价形式Py_TOUPPER(c)返回c的大写等价形式从源码结构看这些宏在当前仓库中的定义位于 Include/cpython/pyctype.h全部采用查表实现分类判断是对 256 项的特征位表_Py_ctype_table做按位与如Py_ISDIGIT(c)检查PY_CTF_DIGIT位大小写转换则查_Py_ctype_tolower/_Py_ctype_toupper两个 256 项查找表。由于表本身由 Python 构建时静态生成转换结果只覆盖 ASCII 语义且与setlocale完全无关这是解析器、tokenizer 等核心组件能够保持跨平台一致行为的基础之一。六、小结与选用建议这组 API 的共同设计目标是在标准 C 库行为跨平台不一致的地方提供确定性的替代方案。选用时可记住几条经验格式化输出用PyOS_snprintf/PyOS_vsnprintf按“三种返回值情形”判断成功、截断与失败不要尝试snprintf(NULL, 0, ...)式探测整数解析用PyOS_strtoul/PyOS_strtol支持0x/0o/0b前缀与 2–36 进制溢出返回ULONG_MAX/LONG_MAX并置ERANGE浮点数解析用PyOS_string_to_double与float()接受集合一致、不接受首尾空白序列化用PyOS_double_to_stringr格式 Py_DTSF_ADD_DOT_0可复现repr语义记得PyMem_Free释放结果大小写无关比较用PyOS_mystricmp/PyOS_mystrnicmp或 Windows 别名PyOS_stricmp/PyOS_strnicmp字符分类与大小写转换用Py_IS*/Py_TO*宏族避免ctype.h的 locale 敏感性。以上接口的声明分散于 Include/pyerrors.h、Include/longobject.h、Include/pystrtod.h、Include/pystrcmp.h 四个公共头文件其中数字转换与格式化函数均已进入 Stable ABI可放心在Py_LIMITED_API扩展中使用。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考