Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 383 – 系统字符接口中不可解码的字节

作者:
Martin von Löwis <martin at v.loewis.de>
状态:
最终版
类型:
标准跟踪
创建日期:
2009年4月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 将受限于无法准确表示所有数据。不幸的是,对于 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,然后这些 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

最后修改时间:2025-02-01 08:55:40 GMT