PEP 383 – 系统字符接口中的不可解码字节
- 作者:
- Martin von Löwis <martin at v.loewis.de>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2009-04-22
- Python 版本:
- 3.1
- 历史记录:
摘要
在 POSIX 中,文件名、环境变量和命令行参数被定义为字符数据;然而,C API 允许传递任意字节——无论这些字节是否符合特定的编码。本 PEP 提出了一种处理这种不规则性的方法,即通过将字节嵌入到字符字符串中,以一种允许重新创建原始字节字符串的方式。
基本原理
C char 类型是一种数据类型,通常用于表示字符数据和字节。某些 POSIX 接口被指定并广泛理解为在字符数据上操作,但是,系统调用接口对这些数据的编码没有假设,并按原样传递它们。在 Python 3 中,字符字符串使用基于 Unicode 的内部表示,这使得难以像 C 接口那样忽略字节字符串的编码。
另一方面,Microsoft Windows NT 修正了 Unix 的原始设计限制,并在其系统接口中明确说明了这些数据(文件名、环境变量、命令行参数)确实是字符数据,方法是提供基于 Unicode 的 API(为了向后兼容性而保留了基于 C 字符的 API)。
对于 Python 3,一种提出的解决方案是提供两组 API:面向字节的 API 和面向字符的 API,其中面向字符的 API 将被限制为无法准确地表示所有数据。不幸的是,对于 Windows,情况恰好相反:面向字节的接口无法表示所有数据;只有面向字符的 API 可以。因此,想要跨平台支持所有用户数据的库和应用程序必须接受字节和字符的混合,这正是导致 Python 2.x 出现无休止问题的根源。
通过本 PEP,可以对这些数据进行统一的字符处理。通过使用特定的编码算法来实现统一性,这意味着只有在使用相同的编码时,才能将数据转换回 POSIX 系统上的字节。
能够统一地处理这些字符串将允许应用程序编写者从特定于操作系统的细节中抽象出来,并降低一个 API 失败而另一个 API 可以正常工作的风险。
规范
在 Windows 上,Python 使用宽字符 API 来访问面向字符的 API,允许将环境数据直接转换为 Python str 对象(PEP 277)。
在 POSIX 系统上,Python 目前应用区域设置的编码将字节数据转换为 Unicode,对于无法解码的字符会失败。在本 PEP 中,不可解码的字节 >= 128 将表示为单独的代理代码 U+DC80..U+DCFF。低于 128 的字节将产生异常;请参见下面的讨论。
为了转换不可解码的字节,引入了新的错误处理程序(PEP 293)“surrogateescape”,它会生成这些代理。在编码时,错误处理程序会将代理转换回相应的字节。此错误处理程序将在接收或生成文件名、命令行参数或环境变量的任何 API 中使用。
错误处理程序接口扩展为允许编码错误处理程序直接返回字节字符串,除了返回 Unicode 字符串,然后再次进行编码(也请参见下面的讨论)。
Python 3.0 中已经存在的面向字节的接口不受本规范的影响。它们既没有增强也没有弃用。
操作文件名的外部库(如 GUI 文件选择器)也应该根据本 PEP 对其进行编码。
讨论
这种 surrogateescape 编码基于 Markus Kuhn 的想法,他称之为 UTF-8b [3]。
虽然提供了一个统一的 API 来处理不可解码的字节,但此接口的局限性在于,所选的表示形式只有在使用 surrogateescape 错误处理程序将数据转换回字节时才“有效”。使用区域设置的编码和(默认)严格错误处理程序对数据进行编码将引发异常,使用 UTF-8 对数据进行编码将生成无意义的数据。
从其他来源获得的数据可能与本 PEP 生成的 数据冲突。处理此类冲突不在本 PEP 的范围内。
本 PEP 允许在字符字符串中“夹带”字节。如果字节在目标系统上被解释为字符时具有安全关键意义,例如路径名称分隔符,那么这将构成安全风险。出于这个原因,本 PEP 拒绝夹带低于 128 的字节。如果目标系统使用 EBCDIC,则此类夹带的字节可能仍然构成安全风险,允许夹带例如方括号或反斜杠。Python 目前不支持 EBCDIC,因此在实践中这应该不成问题。任何将 Python 移植到 EBCDIC 系统的人可能需要调整错误处理程序,或提出其他方法来解决安全风险。
本规范不支持与 ASCII 不兼容的编码;无法解码的 ASCII 范围内的字节将导致异常。人们普遍认为,此类编码不应该用作区域设置字符集。
对于大多数应用程序,我们假设它们最终会将从系统接口接收到的数据传递回相同的系统接口。例如,一个调用 os.listdir() 的应用程序可能会将结果字符串传递回 os.stat() 或 open() 等 API,然后将其编码回原始字节表示形式。需要处理原始字节字符串的应用程序可以通过使用文件系统编码对字符字符串进行编码,并将“surrogateescape”作为错误处理程序名称来获得这些字符串。例如,一个类似于 os.listdir 的函数,除了接受和返回字节外,将被编写为
def listdir_b(dirname):
fse = sys.getfilesystemencoding()
dirname = dirname.decode(fse, "surrogateescape")
for fn in os.listdir(dirname):
# fn is now a str object
yield fn.encode(fse, "surrogateescape")
本 PEP 提出的对编码错误处理程序接口的扩展是实现“surrogateescape”错误处理程序所必需的,因为存在无法从替换 Unicode 生成所需的字节序列。但是,编码错误处理程序接口目前要求提供替换 Unicode 来代替源字符串中不可编码的 Unicode。然后它会立即对该替换 Unicode 进行编码。在某些错误处理程序中,例如这里提出的“surrogateescape”,为错误处理程序提供预编码的替换字节字符串也更简单、更高效,而不是强制它计算编码器将从中创建所需字节的 Unicode。
已经提出了一些替代方案
- 创建一个新的字符串子类,支持嵌入字节
- 使用不同的转义方案,例如用 NUL 字符进行转义,或映射到不常用的字符。
在这些提案中,用序列 U+0000 U+00XX 转义每个字节 XX 的方法有一个缺点,即编码为 UTF-8 会在 UTF-8 序列中引入一个 NUL 字节。因此,C 库可能会将此解释为字符串终止符,即使字符串继续。特别是,gtk 库在这种情况下会截断文本;其他库可能会出现类似的问题。
参考
版权
本文件已进入公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0383.rst
最后修改: 2023-09-09 17:39:29 GMT