尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CPython C API 深度解析:Codec 注册表、PyCodec_* 编码接口与 Unicode 错误处理器

CPython C API 深度解析:Codec 注册表、PyCodec_* 编码接口与 Unicode 错误处理器 CPython C API 深度解析Codec 注册表、PyCodec_* 编码接口与 Unicode 错误处理器【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文基于 CPython 官方 C API 文档 Doc/c-api/codec.rst 展开系统讲解 C 扩展中操作编码codec注册表的核心 API——PyCodec_Register/PyCodec_Encode/PyCodec_Decoder等函数族并结合 Python/codecs.c、Modules/_codecsmodule.c 与 Lib/codecs.py 的源码实现说明注册表查找流程、Unicode 错误处理回调error handler机制及其在解释器内部的真实调用点。读完本文你将能在 C 扩展中完成编码注册/注销、按名称查找编码器与解码器、处理编码错误并理解这些 API 与标准库codecs模块的对应关系。1. 机制总览编码注册表是如何工作的CPython 的编码注册表codec registry是一个每个解释器实例PyInterpreterState各自拥有一份的状态。在 Python/codecs.c 的_PyCodec_InitRegistry()约 L1566-L1697中可以看到它由三部分组成成员类型作用interp-codecs.search_pathlist按注册顺序存放“搜索函数”search functioninterp-codecs.search_cachedict已解析编码名的缓存键为规范化后的编码名interp-codecs.error_registrydict按名称存放的 Unicode 错误处理回调注册表初始化时CPython 先把 8 个内置错误处理器strict、ignore、replace、xmlcharrefreplace、backslashreplace、namereplace、surrogatepass、surrogateescape装入error_registry然后导入encodings包源码注释写明“导入encodings会回调本模块注册搜索函数所以放在最后一步”。encodings包位于 Lib/encodings/ 目录中的各编码模块会把自己注册为搜索函数——这就是文档强调“注册新搜索函数时会尝试先加载encodings包以确保它始终排在搜索函数列表最前面”的原因。编码名查找的核心实现在_PyCodec_Lookup()Python/codecs.c其流程是规范化normalizestring()L91-L119将编码名转为小写、并把空格替换为连字符支持US ASCII、ISO 8859-1这类含空格/大写的别名查缓存先查search_cache命中直接返回顺序扫描依次调用search_path中的搜索函数函数返回None表示“不认这个编码”继续下一个结果契约搜索函数必须返回4 元组(encoder, decoder, streamreader, streamwriter)或CodecInfo对象否则抛出TypeError“codec search functions must return 4-tuples”L194-L198失败行为全部落空时设置PyExc_LookupErrorunknown encoding: %sL202-L205并返回NULL写缓存成功则将编码名 intern 并写入缓存。2. 注册与注销PyCodec_Register/PyCodec_Unregisterint PyCodec_Register(PyObject *search_function); int PyCodec_Unregister(PyObject *search_function); /* 3.10 新增 */PyCodec_RegisterPython/codecs.c把可调用对象追加到search_path内部用FT_MUTEX_LOCK加锁并PyList_Append。源码断言 search function 的引用计数会被此函数增加见 Include/codecs.h 注释传入NULL触发PyErr_BadArgument不可调用对象抛出TypeError。PyCodec_UnregisterL55-L863.10 加入遍历列表找到同一对象后移除并清空整个search_cache因为缓存可能依赖该搜索函数未注册则什么都不做成功返回 0出错返回 -1 并置异常。注意一个细节解释器关闭期间 codec 状态若已被清理initialized ! 1函数直接返回 0。Python 层对应入口是内置模块_codecs的register/unregisterModules/_codecsmodule.c标准库 Lib/codecs.py 再将其封装为codecs.register()。文档同时给出了搜索函数的约定接受一个全小写的编码名参数返回None或 4 元组/CodecInfo。PyCodec_KnownEncodingint PyCodec_KnownEncoding(const char *encoding); /* 返回 1 或 0永不失败 */实现见 Python/codecs.c内部调用_PyCodec_Lookup失败时PyErr_Clear()并返回 0成功返回 1。适合在抛出异常前做“编码是否可用”的探测。3. 通用编解码PyCodec_Encode/PyCodec_DecodePyObject *PyCodec_Encode(PyObject *object, const char *encoding, const char *errors); PyObject *PyCodec_Decode(PyObject *object, const char *encoding, const char *errors);二者是“查编码器 调用编码器”的组合封装PyCodec_Encode先经PyCodec_Encoder(encoding)取出 4 元组第 0 项encoder找不到时如文档所述抛出LookupError_PyCodec_Lookup实际设置的是PyExc_LookupError属于KeyError的父类文档在 Codec lookup 一节记作KeyError见 Include/codecs.h 与 Python/codecs.c再由_PyCodec_EncodeInternalL402-L440构造参数元组并调用 encoder。errors非NULL时作为第二参数传入用该编码注册的错误处理策略为NULL时使用 codec 的默认策略。一个容易踩坑的契约encoder/decoder 被调用后必须返回(result_object, consumed)两元组否则 C 层抛TypeError“encoder must return a tuple (object, integer)”。CPython 自身的内置编码器都遵守这一点例如 Modules/_codecsmodule.c 中utf_8_decode返回(decoded, consumed)元组。Python 侧_codecs.encode/_codecs.decodeModules/_codecsmodule.c同样直接转发到这两个 C API默认编码utf-8、默认错误策略strict。4. Codec lookup API按名称取出六种组件文档“Codec lookup API”一节声明了 6 个函数完整声明见 Include/codecs.h函数返回PyCodec_Encoder(encoding)编码器函数4 元组下标 0PyCodec_Decoder(encoding)解码器函数下标 1PyCodec_IncrementalEncoder(encoding, errors)IncrementalEncoder实例PyCodec_IncrementalDecoder(encoding, errors)IncrementalDecoder实例PyCodec_StreamReader(encoding, stream, errors)已包装stream的StreamReaderPyCodec_StreamWriter(encoding, stream, errors)已包装stream的StreamWriter实现上的要点Python/codecs.c大小写不敏感所有查找都经过前述规范化全小写 空格转连字符因此UTF-8、utf_8、utf-8走同一缓存键增量编解码器由codec_makeincrementalcodecL281-L297生成取出CodecInfo的incrementalencoder/incrementaldecoder工厂属性errors非NULL时以字符串参数调用否则零参调用流编解码器由codec_getstreamcodecL316-L335生成直接取 4 元组下标 2/3 的类并用传入的stream实例化PyObject_CallFunction(codeccls, Os, stream, errors)——即这两个函数返回的不是工厂而是已绑定流的 codec 对象。CodecInfo的字段布局encode/decode/incrementalencoder/incrementaldecoder/streamreader/streamwriter可对照 Lib/codecs.py 的CodecInfo(tuple)类IncrementalEncoder/IncrementalDecoder/BufferedIncrementalEncoder等基类接口encode(input, final)、getstate/setstate也在同一文件中定义是 C 扩展自定义流式编码时的 Python 层参考。5. Unicode 错误处理器注册表PyCodec_RegisterError与内置处理器5.1 注册与查找int PyCodec_RegisterError(const char *name, PyObject *error); PyObject *PyCodec_LookupError(const char *name); /* name 为 NULL 时返回 strict */PyCodec_RegisterErrorPython/codecs.c校验回调可调用后写入error_registry成功返回 0、失败返回 -1PyCodec_LookupErrorL650-L666特殊处理NULL——返回strict处理器未知名称设置LookupError。回调契约文档与实现一致codec 遇到无法编码/解码的序列时调用该回调参数是UnicodeEncodeError/UnicodeDecodeError/UnicodeTranslateError实例其中携带出问题的序列及其在原串中的偏移提取信息可用 Unicode 异常 API。回调要么重新抛出该异常要么返回两元组(replacement, resume_offset)——替换文本以及应继续编解码的偏移量。所有内置处理器最后都Py_BuildValue((Nn), res, end)兑现这一契约如 Python/codecs.c。解释器内部真实调用点TextIOWrapperio模块在 Modules/_io/textio.c 中调用PyCodec_LookupError(name)解析errors参数对应的处理器这正是 C API 的教科书式用途。5.2 六个公开的错误处理器 APIAPI行为适用异常PyCodec_StrictErrors直接抛出传入的异常非异常实例时抛TypeError任意PyCodec_IgnoreErrors返回(空串, end)跳过坏数据继续Encode/Translate/DecodePyCodec_ReplaceErrors编码错误替换为?按出错序列长度填充解码错误替换为 UFFFDEncode/Decode/TranslatePyCodec_XMLCharRefReplaceErrors替换为 XML 数字字符引用#nnn;仅 EncodePyCodec_BackslashReplaceErrors替换为\x/\u/\U转义解码错误按字节生成\xNNEncode/Decode/TranslatePyCodec_NameReplaceErrors3.5替换为\N{...}具名转义查不到名称的码位回退到十六进制转义仅 Encode源码细节值得注意replace在编码与解码路径行为不同编码错误把每个问题字符替换为?_PyCodec_ReplaceUnicodeEncodeErrorPython/codecs.c解码错误则替换为 1 个 UFFFDL846-L858与 Lib/codecs.py 中Codec类文档字符串的描述完全一致backslashreplace的十六进制宽度由codec_handler_unicode_hex_width决定≥0x10000 用\U8 位、≥0x100 用\u4 位、否则\x2 位L691-L706这解释了为何backslashreplace输出中从不出现 6 位形式namereplace依赖 Unicode 名称库 CAPI_PyUnicode_GetNameCAPIL1081-L1084做具名查询头文件中PyCodec_NameReplaceErrors被Py_LIMITED_API 0x03050000条件保护Include/codecs.h即 limited API 下需 3.5 及以上才可见与文档versionadded:: 3.5一致注册表里其实还有surrogatepass与surrogateescape两个处理器Python/codecs.c但它们没有对应的公开PyCodec_*函数以static方式仅通过error_registry按名使用——C 扩展仍可通过PyCodec_LookupError(surrogateescape)获取它们。5.3 在 C 扩展中注册自定义错误处理器示例/* 回调收到 UnicodeEncodeError/DecodeError/TranslateError 实例 */ static PyObject *my_escape_handler(PyObject *Py_UNUSED(self), PyObject *exc) { /* 简单实现像 ignore 一样跳过但可通过 PyUnicode*Error_* 系列函数读取 exc 中的对象与偏移 */ Py_ssize_t start, end; if (PyUnicodeDecodeError_GetStart(exc, start) 0 || PyUnicodeDecodeError_GetEnd(exc, end) 0) return NULL; return Py_BuildValue((Nn), PyUnicode_FromString(), end); } static PyMethodDef my_error_methods[] { {my_escape_error, my_escape_handler, METH_O, NULL}, {NULL, NULL, 0, NULL} }; /* 在模块初始化中 */ PyObject *fn PyCFunction_NewEx(my_error_methods[0].def, NULL, NULL); PyCodec_RegisterError(my_escape, fn); Py_DECREF(fn); /* 注册表持有自己的引用 */之后codecs.decode(data, utf-8, my_escape)或 C 扩展里PyCodec_Decode(obj, utf-8, my_escape)都会路由到你的处理器。6. 工具变量Py_hexdigitsconst char *Py_hexdigits; /* 0123456789abcdef3.3 起提供 */定义于 Python/codecs.c在 limited API 下不可见Include/codecs.h 中由#ifndef Py_LIMITED_API保护。它是解释器内部生成十六进制转义的标准工具backslashreplace处理器Python/codecs.c与内置escape编码器Modules/_codecsmodule.c都直接查表Py_hexdigits[(c 0xf0) 4]。C 扩展中做\x/\u/\U格式化时复用它可以避免自造字符串并与解释器行为保持一致。7. 与 Python 层codecs模块的对应关系三层结构值得在调试时牢记C 核心注册表Python/codecs.c —— 本文主角持有search_path/search_cache/error_registry内置模块_codecsModules/_codecsmodule.c —— 文件头注释明确“此模块不应被直接导入”除register/unregister/lookup/encode/decode外还提供全部 C 实现的基础编码器utf_7/8/16/32家族含utf_16_ex/utf_32_ex暴露 byteorder 的非标准版本、latin_1、ascii、charmap、unicode_escape/raw_unicode_escape以及平台相关的mbcs/oem/code_pageWindowsMS_WINDOWS与iconvHAVE_ICONV标准库codecsLib/codecs.py ——from _codecs import *后叠加CodecInfo、Codec、IncrementalEncoder/Decoder、StreamReader/Writer、BOM 常量BOM_UTF8、BOM_UTF16_LE等与register_error/lookup_error等 Python 封装。因此C 扩展中用PyCodec_StreamReader拿到的对象与 Python 里codecs.getreader(utf-8)(file)构造的是同一种东西。8. 小结与实践要点注册表按解释器隔离多线程下修改search_path有互斥锁保护PyCodec_Unregister会连带清缓存3.10在动态加载/卸载自定义编码模块的扩展中尤其有用所有查找 API 对编码名大小写不敏感且空格等价于连字符自定义搜索函数收到的参数永远是规范化后的小写串PyCodec_Encode/PyCodec_Decode要求目标函数返回(result, consumed)两元组errors传NULL表示使用 codec 默认策略错误处理器回调二选一抛出异常或返回(replacement, offset)PyCodec_LookupError(NULL)等价于取strictPyCodec_KnownEncoding是唯一“永不失败”的探测函数surrogatepass/surrogateescape无公开 C 函数但可按名称从错误注册表取出。以上结论均可在 Python/codecs.c、Include/codecs.h、Modules/_codecsmodule.c、Lib/codecs.py 与 Modules/_io/textio.c 中逐一对照验证。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表