首页
/ CPython Unicode C API 详解:PEP 393 紧凑表示、内置编解码器与 PyUnicodeWriter

CPython Unicode C API 详解:PEP 393 紧凑表示、内置编解码器与 PyUnicodeWriter

2026-09-06 13:10:58作者:齐冠琰

本文基于 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 处。kinddata 必须分别来自 PyUnicode_KINDPyUnicode_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_WriteCharPyUnicode_CopyCharactersPyUnicode_FillPyUnicode_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) strUTF-8 解释并复制到新对象。返回值可能是共享对象,不可修改其数据。以下情况抛 SystemErrorsize < 0strNULLsize > 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) 解码 bytesbytearray 及其他 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 标志)

整型转换(diouxX)使用的长度修饰符(默认 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*

转换说明符完整清单:

说明符 参数类型 说明
% 字面量 %
diuoxX 由长度修饰符指定 有符号十进制 / 无符号十进制 / 八进制 / 十六进制(小写、大写)
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):

  1. 宽度的单位是字符数而非字节数;精度的单位对 %s/%V(当 PyObject* 参数为 NULL 时)是字节数或 wchar_t 项数(l 修饰符时),对 %A%U%S%R%V(参数非 NULL 时)是字符数。
  2. 与 C printf 不同,对整型转换(diuoxX给出精度时 0 标志依然生效

版本演进:3.2 支持 %lld/%llu;3.3 支持 %li%lli%zi;3.4 为 %s%A%U%V%S%R 增加宽度与精度;3.12 增加 oX 说明符与 jt 修饰符,长度修饰符开始作用于所有整型转换,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_leftNULL 并设置异常;成功时 *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_KINDmemset(配合断言 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)两种错误处理器,errorsNULL 时用 "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)编码为 bytesunicode 不能含内嵌空字符;errorsNULL"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 编码为 bytesbytes 对象原样输出。result 必须是 PyObject*(或 PyBytesObject* 类型 C 变量的地址。成功时该变量被设置为指向 bytes 对象的新强引用,不再使用时必须释放,函数返回非零值(Py_CLEANUP_SUPPORTED),且结果中不允许出现内嵌空字节;失败返回 0 并设置异常。若 objNULL,函数释放 result 变量中已有的强引用并返回 1(3.1 加入,3.6 起接受 path-like 对象)。

对应的解码方向用 PyUnicode_FSDecoder

  • int PyUnicode_FSDecoder(PyObject *obj, void *result):将 bytes 对象(直接或经由 os.PathLike)用 PyUnicode_DecodeFSDefaultAndSize 解码为 strstr 原样输出。result 必须是 PyObject*(或 PyUnicodeObject*)变量地址,成功时设置为指向 Unicode 对象的新强引用(不允许内嵌空字符),返回非零值并置 Py_CLEANUP_SUPPORTED;失败返回 0 并设置异常。objNULL 时释放 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) 最多拷贝 sizewchar_t(不含可能的结尾 null)到 wstr,返回拷贝数或 -1。wstrNULL 时返回存储全部内容(含结尾 null)所需的尺寸。结果可能没有 null 结尾,且可能包含 null 字符(用于多数 C 函数时会被截断),收尾由调用方负责
wchar_t *PyUnicode_AsWideCharString(PyObject *unicode, Py_ssize_t *size) 转为宽字符串,输出总是 null 结尾。sizeNULL 时写入宽字符数(不含结尾 null);结果字符串若含 null 字符,多数 C 函数会截断。sizeNULL 且结果含 null 字符时(3.7 起)抛 ValueError。缓冲区由 PyMem_Malloc 分配,需用 PyMem_Free 释放;分配失败返回 NULL 并抛 MemoryError(3.2 加入)

六、内置编解码器(Built-in Codecs)

Python 提供一组用 C 编写以获得速度的内置编解码器,均可通过以下函数直接使用。许多 API 接收 encodingerrors 两个参数,语义与内置 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) consumedNULL 时行为同 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 编码指针并把字节数存入 *sizesize 可为 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_DecodeUTF32PyUnicode_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);

byteorderNULL 时,解码器以给定字节序开始:*byteorder == -1 小端、0 本端(native)、1 大端。当为 0 且输入开头是 BOM 时,解码器切换到 BOM 声明的字节序,且 BOM 不拷入结果字符串;当为 -1 或 1 时,遇到的 BOM 会被原样拷入输出(UTF-16 情况下表现为 U+FEFF 或 U+FFFE 字符)。完成后 *byteorder 被更新为输入数据结尾处的当前字节序。byteorderNULL 时以 native 模式开始。两者出错均返回 NULL

对应的 Stateful 版本(PyUnicode_DecodeUTF32Stateful / PyUnicode_DecodeUTF16Stateful):consumedNULL 时,结尾不完整的字节序列(字节数不为 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 解码。mappingNULL 时按 Latin-1 解码。mapping 须把字节 ordinal(0–255)映射为 Unicode 字符串、整数(作为 Unicode ordinal 解释)或 None;引发 LookupError 的未映射字节、以及映射到 None0xFFFE'\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) consumedNULL 时不解码结尾的 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 字符串列表;sepNULL 时按空白子串分割;最多做 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;abstr 时设置 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) 富比较,opPy_GTPy_GEPy_EQPy_NEPy_LTPy_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 是否包含于 unicodesubstr 必须能强制转换为单元素 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_FromStringPyUnicode_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) 丢弃内部缓冲区并销毁实例;writerNULL 时是空操作;此后实例无效
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 时用 strlenstr 必须只含 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 并写入;objNULL 时写入 "<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 解码写入(errorsNULL 时用 strict);consumedNULL 时成功时写入已解码字节数,为 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_UNICODEwchar_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_EqualPyUnicodeWriter%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)确认目标版本的可用性。

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