CPython Unicode C API 详解:PEP 393 紧凑表示、内置编解码器与 PyUnicodeWriter
本文基于 CPython 官方 C API 参考文档 Doc/c-api/unicode.rst,系统讲解 Python str 类型在 C 层面的完整接口:从 PEP 393 引入的紧凑 Unicode 表示(UCS1/UCS2/UCS4 自适应存储与 UTF-8 缓存),到字符串创建、填充、比较、查找等全部 PyUnicode_* 函数,再到 locale/文件系统编解码与各类内置 Codec 的调用语义。读完本文,你应当能够在 C 扩展模块中正确地创建、访问和转换 Unicode 对象,选择正确的编解码 API,并利用 3.14 新增的 PyUnicodeWriter 安全地构建字符串。
一、紧凑表示:str 对象的内存设计(PEP 393)
自 Python 3.3 实现 PEP 393 以来,Unicode 对象内部使用多种自适应表示来覆盖全部 Unicode 码点范围,同时保持内存效率:
- 所有码点 < 128 → UCS1(每字符 1 字节)
- 所有码点 < 256 → UCS2(每字符 2 字节)
- 所有码点 < 65536 → 仍可选 UCS2,否则
- 所有码点 < 1114112(完整 Unicode 范围)→ UCS4(每字符 4 字节)
UTF-8 表示是按需创建并缓存在 Unicode 对象内的:调用 PyUnicode_AsUTF8AndSize 后,同一对象后续调用返回同一缓冲区,无需再次编码,缓冲区在对象被回收时统一释放,调用方不负责释放。
需要注意的历史变更:Py_UNICODE 表示自 Python 3.12 起已随废弃 API 一同移除(见 PEP 623)。因此现代 C 代码处理单个码点一律使用 Py_UCS4。
1.1 基础类型与结构体
在 Include/unicodeobject.h 中可以看到三个字符宽度的 typedef 定义:
typedef uint32_t Py_UCS4;
typedef uint16_t Py_UCS2;
typedef uint8_t Py_UCS1;
文档定义的核心基础类型如下:
| 类型/变量 | 说明 |
|---|---|
PyTypeObject PyUnicode_Type |
表示 Python Unicode 类型的 PyTypeObject 实例,在 Python 代码中暴露为 str |
PyTypeObject PyUnicodeIter_Type |
Unicode 迭代器类型,用于遍历字符串对象 |
Py_UCS4 / Py_UCS2 / Py_UCS1 |
32/16/8 位无符号整型 typedef;处理单个 Unicode 字符时使用 Py_UCS4(3.3 加入) |
PyASCIIObject / PyCompactUnicodeObject / PyUnicodeObject |
三种表示 Python Unicode 对象的结构体子类型。绝大多数情况下不应直接使用它们——所有处理 Unicode 对象的 API 都接收和返回 PyObject*(3.3 加入) |
对象当前使用的具体结构可用以下宏判断(宏不会失败;参数不是 Unicode 对象时行为未定义):
PyUnicode_IS_COMPACT(o):o使用PyCompactUnicodeObject结构;PyUnicode_IS_COMPACT_ASCII(o):o使用PyASCIIObject结构(3.3 加入)。
类型检查在头文件中实现为轻量宏。从 Include/unicodeobject.h 的源码结构看:
#define PyUnicode_Check(op) \
PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_UNICODE_SUBCLASS)
#define PyUnicode_CheckExact(op) Py_IS_TYPE((op), &PyUnicode_Type)
PyUnicode_Check(obj):对象是 Unicode 对象或 Unicode 子类型实例时返回真,始终成功;PyUnicode_CheckExact(obj):对象是 Unicode 对象但不是子类型实例时返回真,始终成功。
1.2 快速访问内部只读数据的 API
以下 API 是 C 宏与静态内联函数,用于对"规范表示"(canonical representation,即 UCS1/UCS2/UCS4,而非缓存的 UTF-8)字符串做快速检查与访问。除注明外均自 3.3 加入:
| 函数/宏 | 语义 |
|---|---|
Py_ssize_t PyUnicode_GET_LENGTH(PyObject *unicode) |
返回码点长度。要求对象处于规范表示(不检查) |
Py_UCS1 *PyUnicode_1BYTE_DATA(PyObject *)Py_UCS2 *PyUnicode_2BYTE_DATA(PyObject *)Py_UCS4 *PyUnicode_4BYTE_DATA(PyObject *) |
返回指向规范表示的指针并强制转换为 UCS1/UCS2/UCS4 整型,用于直接访问字符。若实际表示宽度不符,行为未定义;应先调用 PyUnicode_KIND 选择正确的函数 |
PyUnicode_1BYTE_KIND / PyUnicode_2BYTE_KIND / PyUnicode_4BYTE_KIND |
PyUnicode_KIND 的返回值常量,标识每字符 1/2/4 字节。注意 PyUnicode_WCHAR_KIND 已在 3.12 移除 |
int PyUnicode_KIND(PyObject *unicode) |
返回上述 kind 常量,指示该对象每字符存储字节数 |
void *PyUnicode_DATA(PyObject *unicode) |
返回原始 Unicode 缓冲区的 void* 指针 |
void PyUnicode_WRITE(int kind, void *data, Py_ssize_t index, Py_UCS4 value) |
将码点 value 写入 data 的零基 index 处。kind 与 data 必须分别来自 PyUnicode_KIND 与 PyUnicode_DATA,且调用期间必须持有该字符串的引用。不执行任何检查,专为循环内使用设计;所有 PyUnicode_WriteChar 的要求同样适用 |
Py_UCS4 PyUnicode_READ(int kind, void *data, Py_ssize_t index) |
从规范表示读取一个码点,不做任何检查、不调用 ready |
Py_UCS4 PyUnicode_READ_CHAR(PyObject *unicode, Py_ssize_t index) |
带对象检查的单字符读取;连续多次读取时效率低于 PyUnicode_READ(先取 kind/data 再循环调用) |
Py_UCS4 PyUnicode_MAX_CHAR_VALUE(PyObject *unicode) |
返回一个近似但高效的"最大码点"估计值,用于判断基于该字符串创建新字符串时可选用的 kind,比遍历字符串快 |
int PyUnicode_IsIdentifier(PyObject *unicode) |
若字符串是语言定义中的合法标识符(见 Doc/reference/lexical_analysis.rst)返回 1,否则返回 0。3.9 起字符串未就绪时不再调用 Py_FatalError |
unsigned int PyUnicode_IS_ASCII(PyObject *unicode) |
字符串只含 ASCII 字符时返回真,等价于 str.isascii()(3.2 加入) |
Py_hash_t PyUnstable_Unicode_GET_CACHED_HASH(PyObject *str) |
若 PyObject_Hash 的哈希值已缓存且立即可用则返回它,否则不设置异常地返回 -1。str 不是字符串时行为未定义;对象哈希何时被缓存无任何保证 |
二、字符属性宏与代换符处理
Unicode 提供大量字符属性,最常用的属性通过下列宏暴露(根据 Python 配置映射到不同 C 函数实现),均以 Py_UCS4 ch 为参数,返回 1 或 0:
| 判定宏 | 语义 |
|---|---|
Py_UNICODE_ISSPACE(ch) |
空白字符 |
Py_UNICODE_ISLOWER(ch) / Py_UNICODE_ISUPPER(ch) / Py_UNICODE_ISTITLE(ch) |
小写 / 大写 / 标题大小写字符 |
Py_UNICODE_ISLINEBREAK(ch) |
换行字符 |
Py_UNICODE_ISDECIMAL(ch) / Py_UNICODE_ISDIGIT(ch) / Py_UNICODE_ISNUMERIC(ch) |
十进制数字 / 数字 / 数值字符 |
Py_UNICODE_ISALPHA(ch) / Py_UNICODE_ISALNUM(ch) |
字母 / 字母数字 |
Py_UNICODE_ISPRINTABLE(ch) |
可打印字符(str.isprintable 的语义) |
快速字符转换 API(不抛异常,失败时按说明返回 -1 或 -1.0):
Py_UCS4 Py_UNICODE_TOLOWER(ch)/TOUPPER(ch)/TOTITLE(ch):转为小写/大写/标题大写;int Py_UNICODE_TODECIMAL(ch):转为十进制正整数,失败返回 -1;int Py_UNICODE_TODIGIT(ch):转为单个数字整数,失败返回 -1;double Py_UNICODE_TONUMERIC(ch):转为double,失败返回 -1.0。
代换符(surrogate pair)相关 API,用于 UTF-16 场景:
int Py_UNICODE_IS_SURROGATE(Py_UCS4 ch); /* 0xD800 <= ch <= 0xDFFF */
int Py_UNICODE_IS_HIGH_SURROGATE(Py_UCS4 ch); /* 0xD800 <= ch <= 0xDBFF */
int Py_UNICODE_IS_LOW_SURROGATE(Py_UCS4 ch); /* 0xDC00 <= ch <= 0xDFFF */
Py_UCS4 Py_UNICODE_HIGH_SURROGATE(Py_UCS4 ch); /* 由 [0x10000, 0x10FFFF] 的码点求高代换符 */
Py_UCS4 Py_UNICODE_LOW_SURROGATE(Py_UCS4 ch); /* 同上,求低代换符 */
Py_UCS4 Py_UNICODE_JOIN_SURROGATES(Py_UCS4 high, Py_UCS4 low); /* 拼接成单个码点 */
Py_UNICODE_JOIN_SURROGATES 要求 high 在 [0xD800, 0xDBFF]、low 在 [0xDC00, 0xDFFF] 范围内。
三、创建与访问 Unicode 字符串
3.1 底层创建:PyUnicode_New 及其"未就绪"约束
PyObject* PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar);
创建新的 Unicode 对象。maxchar 应为将要写入字符串的真实最大码点;作为近似也可以向上取整到 127、255、65535、1114111 序列中最近的值——这正是 PEP 393 选择存储宽度的依据。出错时设置异常并返回 NULL。
创建之后,字符串可以用 PyUnicode_WriteChar、PyUnicode_CopyCharacters、PyUnicode_Fill、PyUnicode_WRITE 等填充。由于字符串按设计是不可变的,填充期间严禁"使用"它。具体而言,在写入最终内容之前,该字符串:
- 不得被计算哈希;
- 不得被转换为 UTF-8 或其他非规范表示(如调用
PyUnicode_AsUTF8AndSize); - 不得改变其引用计数;
- 不得共享给可能执行上述任一操作的代码。
该清单并不穷尽。为避免意外暴露"写了一半"的字符串对象,官方推荐改用 PyUnicodeWriter API(见第九节)或下面的 PyUnicode_From* 系列函数。
3.2 从缓冲区创建
| 函数 | 语义与要点 |
|---|---|
PyUnicode_FromKindAndData(int kind, const void *buffer, Py_ssize_t size) |
以给定 kind(PyUnicode_1BYTE_KIND 等)与每字符 1/2/4 字节缓冲区创建字符串。必要时复制并转换为规范表示:例如一个 UCS4 缓冲区若实际只含 UCS1 范围的码点,会被收窄为 UCS1 存储(3.3 加入) |
PyUnicode_FromStringAndSize(const char *str, Py_ssize_t size) |
将 str 按 UTF-8 解释并复制到新对象。返回值可能是共享对象,不可修改其数据。以下情况抛 SystemError:size < 0;str 为 NULL 且 size > 0(3.12 起禁止该组合) |
PyUnicode_FromString(const char *str) |
从以空字符结尾的 UTF-8 缓冲区创建 |
PyUnicode_FromOrdinal(int ordinal) |
由码点创建单字符字符串;ordinal 必须在 range(0x110000) 内,否则抛 ValueError |
PyUnicode_FromObject(PyObject *obj) |
若 obj 是 Unicode 子类型则复制为真正的 Unicode 对象;若已是非子类型 Unicode 对象则返回一个新的强引用。非 Unicode 对象抛 TypeError |
PyUnicode_FromEncodedObject(PyObject *obj, const char *encoding, const char *errors) |
解码 bytes、bytearray 及其他 bytes-like 对象;encoding/errors 均可为 NULL 使用默认值(默认编码即 UTF-8)。其他任何对象(包括 Unicode 对象)置 TypeError。出错返回 NULL,调用方负责 decref 返回值 |
PyUnicode_FromWideChar(const wchar_t *wstr, Py_ssize_t size) |
从 wchar_t 缓冲区创建;size 传 -1 时用 wcslen 自行计算长度。仅适用于支持 wchar_t 的平台 |
3.3 格式化创建:PyUnicode_FromFormat
PyObject *PyUnicode_FromFormat(const char *format, ...) 接收 printf 风格格式串,先计算结果字符串大小再一次性分配返回。可变参数必须是 C 类型,且与 ASCII 格式串中的转换字符一一对应。格式说明符的组成顺序为:% → 转换标志(可选)→ 最小字段宽度(可选,* 表示宽度取自下一个 int 参数)→ 精度(可选,* 表示取自下一个 int 参数)→ 长度修饰符(可选)→ 转换类型。
转换标志:
| 标志 | 含义 |
|---|---|
0 |
数值转换零填充 |
- |
左对齐(同时给出时覆盖 0 标志) |
整型转换(d、i、o、u、x、X)使用的长度修饰符(默认 int):
| 修饰符 | 对应类型 |
|---|---|
l |
long / unsigned long |
ll |
long long / unsigned long long |
j |
intmax_t / uintmax_t |
z |
size_t / ssize_t |
t |
ptrdiff_t |
对 s/V 转换而言,长度修饰符 l 表示参数类型为 const wchar_t*。
转换说明符完整清单:
| 说明符 | 参数类型 | 说明 |
|---|---|---|
% |
— | 字面量 % |
d、i、u、o、x、X |
由长度修饰符指定 | 有符号十进制 / 无符号十进制 / 八进制 / 十六进制(小写、大写) |
c |
int |
单个字符 |
s |
const char* 或 const wchar_t* |
以空字符结尾的 C 字符数组 |
p |
const void* |
指针的十六进制表示,保证以 0x 开头(与 printf("%p") 基本等价) |
A |
PyObject* |
调用 ascii() 的结果 |
U |
PyObject* |
Unicode 对象 |
V |
PyObject*、const char* 或 const wchar_t* |
第一个参数为 Unicode 对象(可为 NULL),第二个为以空字符结尾的 C 数组,第一个为 NULL 时使用第二个 |
S |
PyObject* |
PyObject_Str 的结果 |
R |
PyObject* |
PyObject_Repr 的结果 |
T / #T |
PyObject* |
对象类型的完整限定名(调用 PyType_GetFullyQualifiedName);#T 用冒号分隔模块名与限定名(3.13 加入) |
N / #N |
PyTypeObject* |
类型的完整限定名;#N 用冒号分隔模块名(3.13 加入) |
两个重要注意事项(来自文档原文的 note):
- 宽度的单位是字符数而非字节数;精度的单位对
%s/%V(当PyObject*参数为NULL时)是字节数或wchar_t项数(l修饰符时),对%A、%U、%S、%R、%V(参数非NULL时)是字符数。 - 与 C
printf不同,对整型转换(d、i、u、o、x、X)给出精度时0标志依然生效。
版本演进:3.2 支持 %lld/%llu;3.3 支持 %li、%lli、%zi;3.4 为 %s、%A、%U、%V、%S、%R 增加宽度与精度;3.12 增加 o、X 说明符与 j、t 修饰符,长度修饰符开始作用于所有整型转换,l 开始作用于 s/V,并支持可变宽度/精度 * 与标志 -——无法识别的格式字符从 3.12 起设置 SystemError(此前会把剩余格式串原样拷贝、丢弃多余参数);3.13 增加 %T、%#T、%N、%#N。
PyUnicode_FromFormatV(const char *format, va_list vargs) 与其完全相同,只是接收恰好两个参数。
一个典型用法示例:
PyObject *msg = PyUnicode_FromFormat("line %zd: %R", lineno, obj);
if (msg == NULL) {
return -1; /* 异常已设置 */
}
/* ... 使用 msg ... */
Py_DECREF(msg);
3.4 追加、映射表与工具函数
void PyUnicode_Append(PyObject **p_left, PyObject *right):将right追加到*p_left末尾。*p_left必须指向 Unicode 对象的强引用,函数会"偷走"(steal)该引用;出错时把*p_left置NULL并设置异常;成功时*p_left指向结果的强引用。void PyUnicode_AppendAndDel(PyObject **p_left, PyObject *right):与PyUnicode_Append相同,但额外将right的引用计数减一。PyObject *PyUnicode_BuildEncodingMap(PyObject *string):给定至多 256 个字符的 Unicode 字符串(编码表),返回适合解码自定义单字节编码的紧凑内部映射对象或字典(字符 ordinal → 字节值);非法输入抛TypeError并返回NULL(3.2 加入)。const char *PyUnicode_GetDefaultEncoding(void):返回默认字符串编码名,即"utf-8",对应sys.getdefaultencoding。返回的字符串无需释放,有效至解释器关闭。Py_ssize_t PyUnicode_GetLength(PyObject *unicode):返回码点长度,出错返回 -1 并设置异常(3.3 加入)。
3.5 填充、复制与调整大小
这些 API 均要求目标字符串"尚未被使用"(约束同 PyUnicode_New):
| 函数 | 语义 |
|---|---|
Py_ssize_t PyUnicode_CopyCharacters(PyObject *to, Py_ssize_t to_start, PyObject *from, Py_ssize_t from_start, Py_ssize_t how_many) |
从一个 Unicode 对象拷贝字符到另一个,必要时做字符宽度转换,可能时回退为 memcpy。出错返回 -1 并设置异常,否则返回拷贝的字符数(3.3 加入) |
int PyUnicode_Resize(PyObject **unicode, Py_ssize_t length) |
将 *unicode 调整到新长度(码点数)。优先原地扩容(通常比分配新串再拷贝快),否则新建字符串。成功时 *unicode 指向新对象并返回 0;失败返回 -1 并设置异常,*unicode 保持不变。注意该函数不检查内容,结果可能不是规范表示 |
Py_ssize_t PyUnicode_Fill(PyObject *unicode, Py_ssize_t start, Py_ssize_t length, Py_UCS4 fill_char) |
用 fill_char 填充 unicode[start:start+length]。若 fill_char 超过字符串最大字符,或字符串引用计数大于 1,则失败。返回写入的字符数,出错返回 -1 并设置异常(3.3 加入) |
int PyUnicode_WriteChar(PyObject *unicode, Py_ssize_t index, Py_UCS4 character) |
在零基 index 写入字符,成功 0、失败 -1(带异常)。该函数检查对象是否为 Unicode 对象、索引是否越界、引用计数是否为 1;PyUnicode_WRITE 是不检查的快速版,检查责任由调用方承担(3.3 加入) |
Py_UCS4 PyUnicode_ReadChar(PyObject *unicode, Py_ssize_t index) |
带检查的单字符读取:校验对象类型与索引边界(PyUnicode_READ_CHAR 则不检查)。成功返回字符,出错返回 -1 并设置异常(3.3 加入) |
PyObject *PyUnicode_Substring(PyObject *unicode, Py_ssize_t start, Py_ssize_t end) |
返回 [start, end) 子串;不支持负索引。出错设置异常并返回 NULL(3.3 加入) |
Py_UCS4 *PyUnicode_AsUCS4(PyObject *unicode, Py_UCS4 *buffer, Py_ssize_t buflen, int copy_null) |
将字符串拷入 UCS4 缓冲区,copy_null 置位时包含结尾空字符。buflen 小于字符串长度时抛 SystemError;出错返回 NULL,成功返回 buffer(3.3 加入) |
Py_UCS4 *PyUnicode_AsUCS4Copy(PyObject *unicode) |
用 PyMem_Malloc 分配新 UCS4 缓冲区并拷贝,总是追加一个额外的空码点。分配失败返回 NULL 并设置 MemoryError(3.3 加入) |
关于"按 kind 分派"的内部机制,从源码结构看,Include/internal/pycore_unicodeobject.h 中的 _PyUnicode_Fill 内联函数展示了标准写法:对 PyUnicode_1BYTE_KIND 用 memset(配合断言 value <= 0xff)、对 2/4 字节宽度用显式循环逐字符写入,最后 Py_UNREACHABLE() 兜底——这与文档中 PyUnicode_Fill 的失败条件(fill_char 超过字符串最大字符)一一对应。
四、Locale 编码与文件系统编码
4.1 Locale 编码
用于解码来自操作系统(终端、环境等)的文本:
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_DecodeLocaleAndSize(const char *str, Py_ssize_t length, const char *errors) |
在 Android 与 VxWorks 上按 UTF-8 解码,其他平台按当前 locale 编码解码。支持 "strict" 与 "surrogateescape"(PEP 383)两种错误处理器,errors 为 NULL 时用 "strict"。str 必须以空字符结尾且不能含内嵌空字符。解码文件名应改用 PyUnicode_DecodeFSDefaultAndSize。该函数忽略 Python UTF-8 模式(3.3 加入;3.7 起 surrogateescape 也改用当前 locale 编码,Android 除外) |
PyObject *PyUnicode_DecodeLocale(const char *str, const char *errors) |
同上,但用 strlen 计算长度(3.3 加入) |
PyObject *PyUnicode_EncodeLocale(PyObject *unicode, const char *errors) |
按 locale 编码(Android/VxWorks 上为 UTF-8)编码为 bytes。unicode 不能含内嵌空字符;errors 为 NULL 时 "strict"。返回 bytes 对象(3.3 加入;3.7 变更同上) |
4.2 文件系统编码(PEP 383 / PEP 529)
在参数解析期间把文件名编码为 bytes,应使用 "O&" 转换器并传入 PyUnicode_FSConverter:
int PyUnicode_FSConverter(PyObject *obj, void *result):PyArg_Parse*转换器。将str对象——直接传入或通过os.PathLike接口获得——用PyUnicode_EncodeFSDefault编码为bytes;bytes对象原样输出。result必须是PyObject*(或PyBytesObject*类型 C 变量的地址。成功时该变量被设置为指向bytes对象的新强引用,不再使用时必须释放,函数返回非零值(Py_CLEANUP_SUPPORTED),且结果中不允许出现内嵌空字节;失败返回 0 并设置异常。若obj为NULL,函数释放result变量中已有的强引用并返回 1(3.1 加入,3.6 起接受 path-like 对象)。
对应的解码方向用 PyUnicode_FSDecoder:
int PyUnicode_FSDecoder(PyObject *obj, void *result):将bytes对象(直接或经由os.PathLike)用PyUnicode_DecodeFSDefaultAndSize解码为str;str原样输出。result必须是PyObject*(或PyUnicodeObject*)变量地址,成功时设置为指向 Unicode 对象的新强引用(不允许内嵌空字符),返回非零值并置Py_CLEANUP_SUPPORTED;失败返回 0 并设置异常。obj为NULL时释放result所指向对象的强引用并返回 1(3.2 加入,3.6 起接受 path-like 对象)。
直接的编解码函数:
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_DecodeFSDefaultAndSize(const char *str, Py_ssize_t size) |
按文件系统编码与错误处理器解码已知长度的字符串(3.6 起使用文件系统错误处理器) |
PyObject *PyUnicode_DecodeFSDefault(const char *str) |
解码以空字符结尾的字符串;长度已知时优先用 AndSize 版本 |
PyObject *PyUnicode_EncodeFSDefault(PyObject *unicode) |
按文件系统编码与错误处理器编码为 bytes;结果可以包含空字节(3.2 加入;3.6 起使用文件系统错误处理器) |
典型 O& 用法(解码字节文件名):
PyObject *path_obj, *result;
if (!PyArg_ParseTuple(args, "O&:open_path", PyUnicode_FSDecoder, &result)) {
return NULL;
}
Py_DECREF(path_obj);
/* 使用 result (str) ... */
Py_DECREF(result);
五、wchar_t 支持
在支持 wchar_t 的平台上,头文件中这三组函数以 #ifdef HAVE_WCHAR_H 条件编译(参见 Include/unicodeobject.h 中的声明与注释):
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_FromWideChar(const wchar_t *wstr, Py_ssize_t size) |
从 wchar_t 缓冲区创建字符串;size 为 -1 时用 wcslen 计算。失败返回 NULL |
Py_ssize_t PyUnicode_AsWideChar(PyObject *unicode, wchar_t *wstr, Py_ssize_t size) |
最多拷贝 size 个 wchar_t(不含可能的结尾 null)到 wstr,返回拷贝数或 -1。wstr 为 NULL 时返回存储全部内容(含结尾 null)所需的尺寸。结果可能没有 null 结尾,且可能包含 null 字符(用于多数 C 函数时会被截断),收尾由调用方负责 |
wchar_t *PyUnicode_AsWideCharString(PyObject *unicode, Py_ssize_t *size) |
转为宽字符串,输出总是 null 结尾。size 非 NULL 时写入宽字符数(不含结尾 null);结果字符串若含 null 字符,多数 C 函数会截断。size 为 NULL 且结果含 null 字符时(3.7 起)抛 ValueError。缓冲区由 PyMem_Malloc 分配,需用 PyMem_Free 释放;分配失败返回 NULL 并抛 MemoryError(3.2 加入) |
六、内置编解码器(Built-in Codecs)
Python 提供一组用 C 编写以获得速度的内置编解码器,均可通过以下函数直接使用。许多 API 接收 encoding 与 errors 两个参数,语义与内置 str 构造函数同名参数一致:
encoding设为NULL时使用默认编码,即 UTF-8;errors设为NULL时使用该编解码器定义的默认错误处理,所有内置编解码器的默认都是"strict"(抛ValueError);- 各编解码器接口风格一致,文档只记录偏离通用签名的部分。
通用宏 Py_UNICODE_REPLACEMENT_CHARACTER 即码点 U+FFFD(替换字符),在 errors="replace" 解码时作为替换字符使用(定义见 Include/unicodeobject.h)。
6.1 通用编解码器
PyObject *PyUnicode_Decode(const char *str, Py_ssize_t size,
const char *encoding, const char *errors);
PyObject *PyUnicode_AsEncodedString(PyObject *unicode,
const char *encoding, const char *errors);
PyUnicode_Decode 解码 size 字节,编解码器通过 Python 编解码器注册表查找(对应 Lib/encodings/ 中注册的全部编码);PyUnicode_AsEncodedString 编码 Unicode 对象并返回 bytes。两者出错时均返回 NULL。
6.2 UTF-8 编解码器
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_DecodeUTF8(const char *str, Py_ssize_t size, const char *errors) |
解码 size 字节 UTF-8 |
PyObject *PyUnicode_DecodeUTF8Stateful(const char *str, Py_ssize_t size, const char *errors, Py_ssize_t *consumed) |
consumed 为 NULL 时行为同 PyUnicode_DecodeUTF8;非 NULL 时结尾不完整的 UTF-8 字节序列不视为错误,这些字节不被解码,已解码字节数存入 *consumed |
PyObject *PyUnicode_AsUTF8String(PyObject *unicode) |
UTF-8 编码返回 bytes,严格错误处理。字符串含代换符(U+D800–U+DFFF)时失败 |
const char *PyUnicode_AsUTF8AndSize(PyObject *unicode, Py_ssize_t *size) |
返回 UTF-8 编码指针并把字节数存入 *size(size 可为 NULL)。返回的缓冲区总是追加了一个空字节(不计入 size),无论字符串本身是否含空码点。出错时设置异常、*size 置 -1(若非 NULL)并返回 NULL;含代换符时失败。UTF-8 表示被缓存在 Unicode 对象中,后续调用返回同一缓冲区;调用方不负责释放,对象被回收时缓冲区随之释放(3.3 加入;3.7 起返回类型变为 const char *;3.10 起进入 Limited API) |
const char *PyUnicode_AsUTF8(PyObject *unicode) |
同 AndSize 版本但不存储长度。注意对内嵌空字符无任何特殊处理:含 null 的字符串返回后会被多数 C 函数截断;若截断是问题,应改用 PyUnicode_AsUTF8AndSize(3.3 加入;3.7 起 const 限定) |
Include/unicodeobject.h 中的声明印证了 Limited API 的门槛:
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
PyAPI_FUNC(const char *) PyUnicode_AsUTF8AndSize(
PyObject *unicode,
Py_ssize_t *size);
#endif
6.3 UTF-32 与 UTF-16 编解码器
PyUnicode_DecodeUTF32 与 PyUnicode_DecodeUTF16 的签名分别为:
PyObject *PyUnicode_DecodeUTF32(const char *str, Py_ssize_t size,
const char *errors, int *byteorder);
PyObject *PyUnicode_DecodeUTF16(const char *str, Py_ssize_t size,
const char *errors, int *byteorder);
byteorder 非 NULL 时,解码器以给定字节序开始:*byteorder == -1 小端、0 本端(native)、1 大端。当为 0 且输入开头是 BOM 时,解码器切换到 BOM 声明的字节序,且 BOM 不拷入结果字符串;当为 -1 或 1 时,遇到的 BOM 会被原样拷入输出(UTF-16 情况下表现为 U+FEFF 或 U+FFFE 字符)。完成后 *byteorder 被更新为输入数据结尾处的当前字节序。byteorder 为 NULL 时以 native 模式开始。两者出错均返回 NULL。
对应的 Stateful 版本(PyUnicode_DecodeUTF32Stateful / PyUnicode_DecodeUTF16Stateful):consumed 非 NULL 时,结尾不完整的字节序列(字节数不为 4 的倍数、奇数字节、被拆开的代换符对等)不视为错误,已解码字节数存入 *consumed。
编码方向:
PyObject *PyUnicode_AsUTF32String(PyObject *unicode):native 字节序 UTF-32,字符串总以 BOM 开头,严格错误处理;PyObject *PyUnicode_AsUTF16String(PyObject *unicode):native 字节序 UTF-16,同样总以 BOM 开头,严格错误处理。
6.4 UTF-7、Unicode-Escape、Raw-Unicode-Escape
| 编解码器 | 解码 | 编码 |
|---|---|---|
| UTF-7 | PyUnicode_DecodeUTF7(str, size, errors);Stateful 版本对结尾不完整的 base-64 段不报错,已解码字节存入 *consumed |
—(无独立 As*String API) |
| Unicode-Escape | PyUnicode_DecodeUnicodeEscape(str, size, errors) |
PyUnicode_AsUnicodeEscapeString(unicode),严格模式,返回 bytes |
| Raw-Unicode-Escape | PyUnicode_DecodeRawUnicodeEscape(str, size, errors) |
PyUnicode_AsRawUnicodeEscapeString(unicode),严格模式 |
6.5 Latin-1 与 ASCII
- Latin-1 对应前 256 个 Unicode ordinal,编码方向只接受这些码点:
PyUnicode_DecodeLatin1(str, size, errors)与PyUnicode_AsLatin1String(unicode)(严格模式)。 - ASCII 只接受 7 位数据,其余码一律报错:
PyUnicode_DecodeASCII(str, size, errors)与PyUnicode_AsASCIIString(unicode)(严格模式)。
6.6 字符映射编解码器(Charmap)
Charmap 编解码器很特殊:它本身可以承载很多不同编解码器(Lib/encodings/ 包中的大部分标准编解码器正是由此实现)。映射对象只需支持 __getitem__ 映射接口,字典与序列都适用。
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_DecodeCharmap(const char *str, Py_ssize_t length, PyObject *mapping, const char *errors) |
用 mapping 解码。mapping 为 NULL 时按 Latin-1 解码。mapping 须把字节 ordinal(0–255)映射为 Unicode 字符串、整数(作为 Unicode ordinal 解释)或 None;引发 LookupError 的未映射字节、以及映射到 None、0xFFFE 或 '\ufffe' 的字节都视为"未定义映射"并报错 |
PyObject *PyUnicode_AsCharmapString(PyObject *unicode, PyObject *mapping) |
用 mapping 编码为 bytes,严格模式。mapping 须把 Unicode ordinal 映射为 bytes 对象、0–255 的整数或 None;未映射(LookupError)与映射到 None 均报错 |
Unicode→Unicode 的特殊版本:
PyObject *PyUnicode_Translate(PyObject *unicode, PyObject *table, const char *errors):对字符串应用字符映射表并返回结果。表须把 Unicode ordinal 映射为 Unicode ordinal 或 None(删除该字符)。只需提供 __getitem__;未映射的 ordinal(LookupError)原样拷贝。errors 取常规编解码器语义,NULL 表示默认错误处理。
6.7 Windows MBCS 编解码器
这组 API 仅在 Windows 上可用,使用 Win32 MBCS 转换器实现。MBCS(或 DBCS)是一类编码而非单一编码,目标编码由运行该编解码器的机器上的用户设置决定:
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_DecodeMBCS(const char *str, Py_ssize_t size, const char *errors) |
按 MBCS 解码 |
PyObject *PyUnicode_DecodeMBCSStateful(..., Py_ssize_t *consumed) |
consumed 非 NULL 时不解码结尾的 lead byte,已解码字节数存入 *consumed |
PyObject *PyUnicode_DecodeCodePageStateful(int code_page, const char *str, Py_ssize_t size, const char *errors, Py_ssize_t *consumed) |
与 MBCSStateful 相同,但使用 code_page 指定的代码页 |
PyObject *PyUnicode_AsMBCSString(PyObject *unicode) |
MBCS 编码,严格模式 |
PyObject *PyUnicode_EncodeCodePage(int code_page, PyObject *unicode, const char *errors) |
按指定代码页编码;取 MBCS 编码器时传 CP_ACP 代码页(3.3 加入) |
七、字符串操作方法(Methods and Slot Functions)
以下 API 接收 Unicode 对象/字符串作为输入,返回 Unicode 对象或整数;全部在异常时返回 NULL 或 -1:
| 函数 | 语义 |
|---|---|
PyObject *PyUnicode_Concat(PyObject *left, PyObject *right) |
拼接两个字符串,返回新字符串 |
PyObject *PyUnicode_Split(PyObject *unicode, PyObject *sep, Py_ssize_t maxsplit) |
分割为 Unicode 字符串列表;sep 为 NULL 时按空白子串分割;最多做 maxsplit 次分割,负值表示不限;分隔符不包含在结果中。等价于 str.split |
PyObject *PyUnicode_RSplit(...) |
同 Split,但从字符串末尾开始分割。等价于 str.rsplit |
PyObject *PyUnicode_Splitlines(PyObject *unicode, int keepends) |
按换行分割为列表;CRLF 视为一个换行;keepends 为 0 时换行字符不保留 |
PyObject *PyUnicode_Partition(PyObject *unicode, PyObject *sep) |
在 sep 第一次出现处分割,返回三元组(前部、分隔符、后部);找不到时返回(原串、空、空)。sep 不得为空。等价于 str.partition |
PyObject *PyUnicode_RPartition(PyObject *unicode, PyObject *sep) |
在最后一次出现处分割;找不到时返回(空、空、原串) |
PyObject *PyUnicode_Join(PyObject *separator, PyObject *seq) |
用 separator 连接序列中的字符串 |
Py_ssize_t PyUnicode_Tailmatch(PyObject *unicode, PyObject *substr, Py_ssize_t start, Py_ssize_t end, int direction) |
direction == -1 做前缀匹配、1 做后缀匹配,判断 substr 是否匹配 unicode[start:end] 的给定尾端;1/0/-1(错误) |
Py_ssize_t PyUnicode_Find(PyObject *unicode, PyObject *substr, Py_ssize_t start, Py_ssize_t end, int direction) |
在 unicode[start:end] 中查找 substr 第一次出现的位置;direction 1 正向、-1 反向。返回索引;-1 表示未找到;-2 表示出错且已设置异常 |
Py_ssize_t PyUnicode_FindChar(PyObject *unicode, Py_UCS4 ch, Py_ssize_t start, Py_ssize_t end, int direction) |
单字符版 Find(3.3 加入;3.7 起 start/end 调整为 unicode[start:end] 语义) |
Py_ssize_t PyUnicode_Count(PyObject *unicode, PyObject *substr, Py_ssize_t start, Py_ssize_t end) |
unicode[start:end] 中 substr 非重叠出现次数;出错返回 -1 |
PyObject *PyUnicode_Replace(PyObject *unicode, PyObject *substr, PyObject *replstr, Py_ssize_t maxcount) |
最多替换 maxcount 次,-1 表示全部替换 |
int PyUnicode_Compare(PyObject *left, PyObject *right) |
返回 -1/0/1(小于/相等/大于);失败时也返回 -1,应调用 PyErr_Occurred 检查 |
int PyUnicode_Equal(PyObject *a, PyObject *b) |
相等测试:相等 1、不等 0;a 或 b 非 str 时设置 TypeError 并返回 -1;对 str 对象本身总能成功。支持 str 子类型但不遵循自定义 __eq__()(3.14 加入) |
int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *string, Py_ssize_t size) |
与按 UTF-8/ASCII 解释的 C 缓冲比较;Unicode 对象含代换符或 C 串不是合法 UTF-8 时返回 0(假)。不抛异常(3.13 加入) |
int PyUnicode_EqualToUTF8(PyObject *unicode, const char *string) |
同上,用 strlen 计算长度;Unicode 对象含 null 字符时返回 0(3.13 加入) |
int PyUnicode_CompareWithASCIIString(PyObject *unicode, const char *string) |
返回 -1/0/1。建议只传 ASCII 编码串;含非 ASCII 字符时按 ISO-8859-1 解释。不抛异常 |
PyObject *PyUnicode_RichCompare(PyObject *left, PyObject *right, int op) |
富比较,op 取 Py_GT、Py_GE、Py_EQ、Py_NE、Py_LT、Py_LE;成功返回 Py_True/Py_False,类型组合未知返回 Py_NotImplemented,异常返回 NULL |
PyObject *PyUnicode_Format(PyObject *format, PyObject *args) |
等价于 Python 的 format % args |
int PyUnicode_Contains(PyObject *unicode, PyObject *substr) |
判断 substr 是否包含于 unicode;substr 必须能强制转换为单元素 Unicode 字符串;出错返回 -1 |
7.1 字符串驻留(Interning)
| 函数 | 语义 |
|---|---|
void PyUnicode_InternInPlace(PyObject **p_unicode) |
就地驻留 *p_unicode。若已存在相同值的驻留字符串,则把 *p_unicode 指向它(释放旧串引用、新建对驻留串的强引用);否则保留原指针并将其驻留。可从"引用中性"角度理解:你必须拥有传入对象的所有权,调用后不再拥有传入的引用,但新拥有了结果。永不抛异常;出错时保持参数不变。str 子类实例不可驻留——必须满足 PyUnicode_CheckExact(*p_unicode),否则视同其他错误,参数保持不变。驻留字符串并非"不朽"(immortal),必须自行保持对结果的引用 |
PyObject *PyUnicode_InternFromString(const char *str) |
PyUnicode_FromString + PyUnicode_InternInPlace 的组合,适合静态字符串。返回对新驻留串或已有驻留串的(拥有的)强引用。Python 可能长期持有结果引用或将其设为 immortal,从而阻止其及时回收;对无界数量的字符串(如用户输入)驻留,建议直接分别调用 PyUnicode_FromString 与 PyUnicode_InternInPlace |
unsigned int PyUnicode_CHECK_INTERNED(PyObject *str) |
驻留返回非零、未驻留返回 0。str 必须是字符串(不检查),函数总能成功。实现细节:非零返回值可能携带关于"如何"驻留的额外信息,其含义与具体字符串的驻留细节可能在 CPython 版本之间变化 |
八、PyUnicodeWriter:3.14 引入的字符串构建器
PyUnicodeWriter API(3.14 加入)是构建 Python str 对象的安全方式——文档在 PyUnicode_New 一节明确建议用它替代"手动 New + 填充"模式,因为它避免了"半填充字符串被意外使用"的风险。生命周期契约是:实例必须以 PyUnicodeWriter_Finish(成功)或 PyUnicodeWriter_Discard(出错)结束。
| 函数 | 语义 |
|---|---|
PyUnicodeWriter *PyUnicodeWriter_Create(Py_ssize_t length) |
创建实例;length 须 ≥ 0,大于 0 时预分配 length 字符的内部缓冲区。出错设置异常并返回 NULL |
PyObject *PyUnicodeWriter_Finish(PyUnicodeWriter *writer) |
返回最终 str 对象并销毁实例;此后实例无效。出错设置异常并返回 NULL |
void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) |
丢弃内部缓冲区并销毁实例;writer 为 NULL 时是空操作;此后实例无效 |
int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) |
写入单个码点 |
int PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) |
以严格模式从 UTF-8 解码写入;size 为字节数,-1 时用 strlen |
int PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) |
写入纯 ASCII 串;size 为 -1 时用 strlen。str 必须只含 ASCII,含非 ASCII 时行为未定义 |
int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) |
写入宽字符串;size 为宽字符数,-1 时用 wcslen |
int PyUnicodeWriter_WriteUCS4(PyUnicodeWriter *writer, const Py_UCS4 *str, Py_ssize_t size) |
写入 UCS4 串;size 为 UCS4 字符数 |
int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) |
对 obj 调用 PyObject_Str 并写入。若 str 子类重写了 __str__,可用 PyUnicode_FromObject 取回原始字符串再写 |
int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) |
对 obj 调用 PyObject_Repr 并写入;obj 为 NULL 时写入 "<NULL>"(3.14.4 起支持) |
int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) |
写入 str[start:end];str 必须是 str 对象,要求 0 ≤ start ≤ end ≤ 串长 |
int PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) |
同 PyUnicode_FromFormat,但直接写入 writer |
int PyUnicodeWriter_DecodeUTF8Stateful(PyUnicodeWriter *writer, const char *string, Py_ssize_t length, const char *errors, Py_ssize_t *consumed) |
用 errors 错误处理器从 UTF-8 解码写入(errors 为 NULL 时用 strict);consumed 非 NULL 时成功时写入已解码字节数,为 NULL 时结尾不完整的 UTF-8 序列视为错误 |
以上写函数统一约定:成功返回 0;出错设置异常、writer 保持不变并返回 -1。
从源码结构看,内部实现(_PyUnicodeWriter,声明于 Include/internal/pycore_unicodeobject.h)正是建立在前述紧凑表示 API 之上:内联写字符函数先经 _PyUnicodeWriter_Prepare 按需扩容/升级 kind,再通过 PyUnicode_WRITE(writer->kind, writer->data, writer->pos, ch) 直接落到规范表示缓冲区并推进 pos。公开 API 的完整实现位于 Objects/unicodeobject.c(约 1.5 万行,Unicode 类型、编解码器与 writer 的核心实现文件)。
一个典型的安全构建模式:
PyUnicodeWriter *writer = PyUnicodeWriter_Create(64);
if (writer == NULL) {
return NULL;
}
if (PyUnicodeWriter_WriteASCII(writer, "error: ", -1) < 0 ||
PyUnicodeWriter_Format(writer, "code 0x%x (0x%lX)", code, long_code) < 0) {
PyUnicodeWriter_Discard(writer);
return NULL;
}
PyObject *result = PyUnicodeWriter_Finish(writer); /* writer 此后无效 */
if (result == NULL) {
return NULL;
}
/* ... 使用 result ... */
Py_DECREF(result);
九、废弃 API 与迁移注意
文档最后列出的废弃 API:
Py_UNICODE:wchar_t的 typedef,随平台为 16 位或 32 位类型;请直接使用wchar_t。3.13 起弃用、3.16 计划移除。更早期的历史(3.3 之前)它按构建时选择"窄/宽"Unicode 版本而定宽窄。int PyUnicode_READY(PyObject *unicode):什么都不做并返回 0。仅为向后兼容保留,无移除计划。3.10 起标注弃用——自 3.12 起无实际作用;此前对由旧 API(PyUnicode_FromUnicode之类)创建的每个字符串都必须调用它。unsigned int PyUnicode_IS_READY(PyObject *unicode):什么都不做并返回 1。3.14 起标注弃用;此前用于检查是否需要调用PyUnicode_READY。
迁移要点:旧代码中 PyUnicode_READY / PyUnicode_IS_READY 调用可以安全删除(它们现在是空操作);Py_UNICODE 全部替换为 Py_UCS4(读码点)或 wchar_t(宽字符接口)。
十、相关源码与文档索引
| 路径 | 内容 |
|---|---|
| Doc/c-api/unicode.rst | 本文所依据的官方 C API 参考文档 |
| Include/unicodeobject.h | 公开 Unicode API 声明:Py_UCS1/2/4 typedef、PyUnicode_Check 宏、PyUnicode_From* 与全部内置编解码器函数 |
| Include/internal/pycore_unicodeobject.h | 内部 API:_PyUnicode_Fill、_PyUnicodeWriter_* 内联实现、_PyUnicode_FormatAdvancedWriter 等 |
| Objects/unicodeobject.c | str 类型、编解码器与 PyUnicodeWriter 的核心实现(约 15000 行) |
| Lib/encodings/ | Python 层编解码器注册表与各标准编码实现 |
| Doc/library/codecs.rst | encodings/codecs 模块的 Python 层文档(PyUnicode_FromEncodedObject 一节的错误处理细节指向此处) |
适用前提:本文所述接口以当前仓库(CPython 主开发分支,已包含 3.13/3.14 的新增 API,如 PyUnicode_Equal、PyUnicodeWriter、%T/%N 格式符)为准。使用 PyUnicodeWriter 需 3.14+;PyUnicode_AsUTF8AndSize 在 Limited API 下需 3.10+;MBCS 编解码器仅 Windows 可用;wchar_t 接口受平台 HAVE_WCHAR_H 条件编译限制。跨版本开发时请对照各函数的 versionadded/versionchanged 标注(均见 Doc/c-api/unicode.rst)确认目标版本的可用性。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00