PEP 540 – 添加新的 UTF-8 模式
- 作者:
- Victor Stinner <vstinner at python.org>
- BDFL 委托:
- INADA Naoki
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2016年1月5日
- Python 版本:
- 3.7
- 决议:
- Python-Dev 消息
摘要
添加一个新的“UTF-8 模式”以增强 Python 对 UTF-8 的使用。当 UTF-8 模式激活时,Python 将
- 使用
utf-8编码,无论当前平台设置的区域设置如何,并且 - 将
stdin和stdout的错误处理程序更改为surrogateescape。
此模式默认关闭,但在使用“POSIX”区域设置时会自动激活。
添加 -X utf8 命令行选项和 PYTHONUTF8 环境变量来控制 UTF-8 模式。
基本原理
区域设置编码和 UTF-8
Python 3.6 对文件名、环境变量、标准流等使用区域设置编码。区域设置编码继承自区域设置;编码和区域设置紧密耦合。
许多用户从 POSIX 区域设置(又称“C”区域设置)继承 ASCII 编码,但由于各种原因无法更改区域设置。这种编码在 Unicode 支持方面非常有限:任何非 ASCII 字符都可能导致问题。
获取准确的区域设置并不总是那么容易。区域设置在不同的 Linux 发行版、FreeBSD、macOS 等上没有完全相同的名称。而且某些区域设置,例如最近的 C.UTF-8 区域设置,仅受少数平台支持。当前区域设置甚至可以在同一平台上根据上下文而变化;例如,SSH 连接可以使用与同一机器上的文件系统或本地终端编码不同的编码。
另一方面,Python 3.6 在 macOS、Android 和 Windows 上已经默认使用 UTF-8(PEP 529),用于大多数函数——尽管 open() 是一个显著的例外。UTF-8 也是 Python 脚本、XML 和 JSON 文件格式的默认编码。Go 编程语言对所有字符串都使用 UTF-8。
UTF-8 支持在现代平台读写数据方面几乎无处不在。它在 Python 中也具有出色的支持。问题仅仅是区域设置经常配置错误。一个显而易见的解决方案是:忽略区域设置编码并使用 UTF-8。
不可解码字节的直通:surrogateescape
当使用默认的 strict 错误处理程序从 UTF-8 解码字节时,Python 3 会在第一个不可解码字节上引发 UnicodeDecodeError。
Unix 命令行工具,如 cat 或 grep,以及大多数 Python 2 应用程序根本没有这类错误:它们不解码数据,而是将数据作为原始字节序列处理。
Python 3 已经有一个解决方案可以像 Unix 工具和 Python 2 一样工作:surrogateescape 错误处理程序(PEP 383)。它允许将数据视为字节处理,但实际上使用 Unicode;不可解码的字节存储为代理字符。
UTF-8 模式为 stdin 和 stdout 设置 surrogateescape 错误处理程序,因为这些流通常与 Unix 命令行工具相关联。
然而,用户对文件有不同的期望。文件应正确编码,并且当 open() 以错误选项调用时,例如以文本模式打开 JPEG 图片,Python 应尽早失败。出于这些原因,open() 的默认错误处理程序仍为 strict。
默认不作更改以实现最佳向后兼容性
虽然 UTF-8 在大多数情况下是完美的,但有时区域设置编码实际上是最好的编码。
本 PEP 更改了 POSIX 区域设置的行为,因为此区域设置通常等同于 ASCII 编码,而 UTF-8 是一个更好的选择。它不更改其他区域设置的行为,以防止任何风险或回归。
由于用户有责任为这些其他区域设置明确启用新的 UTF-8 模式,因此他们对 UTF-8 模式引起的任何潜在乱码问题负责。
提案
添加一个新的 UTF-8 模式以使用 UTF-8 编码,忽略区域设置编码,并将 stdin 和 stdout 错误处理程序更改为 surrogateescape。
添加新的 -X utf8 命令行选项和 PYTHONUTF8 环境变量。用户可以使用命令行选项 -X utf8 或通过设置环境变量 PYTHONUTF8=1 来明确激活 UTF-8 模式。
此模式默认禁用,并由 POSIX 区域设置启用。用户可以使用命令行选项 -X utf8=0 或通过设置环境变量 PYTHONUTF8=0 来明确禁用 UTF-8 模式。
对于标准流,PYTHONIOENCODING 环境变量优先于 UTF-8 模式。
在 Windows 上,PYTHONLEGACYWINDOWSFSENCODING 环境变量(PEP 529)优先于 UTF-8 模式。
UTF-8 模式的效果
sys.getfilesystemencoding()返回'UTF-8'。locale.getpreferredencoding()返回UTF-8;其 do_setlocale 参数和区域设置编码被忽略。sys.stdin和sys.stdout错误处理程序设置为surrogateescape。
副作用
open()默认使用 UTF-8 编码。但是,它仍然默认使用strict错误处理程序。os.fsdecode()和os.fsencode()使用 UTF-8 编码。- 命令行参数、环境变量和文件名使用 UTF-8 编码。
与区域设置强制转换的关系 (PEP 538)
POSIX 区域设置启用区域设置强制转换(PEP 538)和 UTF-8 模式(PEP 540)。当区域设置强制转换启用时,启用 UTF-8 模式没有额外的效果。
UTF-8 模式与区域设置强制转换具有相同的效果
sys.getfilesystemencoding()返回'UTF-8',locale.getpreferredencoding()返回UTF-8,并且sys.stdin和sys.stdout错误处理程序设置为surrogateescape。
这些更改只影响 Python 代码。但是区域设置强制转换有额外的效果:LC_CTYPE 环境变量和 LC_CTYPE 区域设置被设置为像 C.UTF-8 这样的 UTF-8 区域设置。一个副作用是,非 Python 代码也受区域设置强制转换的影响。这两个 PEP 相互补充。
在像 Centos 7 这样不支持区域设置强制转换的平台上,POSIX 区域设置只启用 UTF-8 模式。在这种情况下,Python 代码使用 UTF-8 编码并忽略区域设置编码,而非 Python 代码使用区域设置编码,对于 POSIX 区域设置通常是 ASCII。
虽然 UTF-8 模式在所有平台上都受支持,并且可以在任何区域设置下启用,但区域设置强制转换并非所有平台都支持,并且仅限于 POSIX 区域设置。
当 PYTHONUTF8 环境变量设置为 1 时,UTF-8 模式仅对 Python 子进程产生影响,而区域设置强制转换设置 LC_CTYPE 环境变量,这会影响所有子进程。
区域设置强制转换方法的优点是它有助于确保二进制扩展模块和子进程中的编码处理与 Python 的编码处理保持一致。UTF-8 模式方法的优点是它允许嵌入应用程序更改解释器的行为,而无需更改进程全局区域设置。
向后兼容性
唯一向后不兼容的更改是 POSIX 区域设置现在默认启用 UTF-8 模式:它现在将使用 UTF-8 编码,忽略区域设置编码,并将 stdin 和 stdout 的错误处理程序更改为 surrogateescape。
附录:编码和错误处理程序
UTF-8 模式更改了 open()、os.fsdecode()、os.fsencode()、sys.stdin、sys.stdout 和 sys.stderr 使用的默认编码和错误处理程序。
编码和错误处理程序
| 函数 | 默认值 | UTF-8 模式或 POSIX 区域设置 |
|---|---|---|
| open() | locale/strict | UTF-8/strict |
| os.fsdecode(),os.fsencode() | locale/surrogateescape | UTF-8/surrogateescape |
| sys.stdin, sys.stdout | locale/strict | UTF-8/surrogateescape |
| sys.stderr | locale/backslashreplace | UTF-8/backslashreplace |
相比之下,Python 3.6 使用
| 函数 | 默认值 | POSIX 区域设置 |
|---|---|---|
| open() | locale/strict | locale/strict |
| os.fsdecode(),os.fsencode() | locale/surrogateescape | locale/surrogateescape |
| sys.stdin, sys.stdout | locale/strict | locale/surrogateescape |
| sys.stderr | locale/backslashreplace | locale/backslashreplace |
Windows 上的编码和错误处理程序
在 Windows 上,编码和错误处理程序是不同的
| 函数 | 默认值 | 传统 Windows 文件系统编码 | UTF-8 模式 |
|---|---|---|---|
| open() | mbcs/strict | mbcs/strict | UTF-8/strict |
| os.fsdecode(),os.fsencode() | UTF-8/surrogatepass | mbcs/replace | UTF-8/surrogatepass |
| sys.stdin, sys.stdout | UTF-8/surrogateescape | UTF-8/surrogateescape | UTF-8/surrogateescape |
| sys.stderr | UTF-8/backslashreplace | UTF-8/backslashreplace | UTF-8/backslashreplace |
相比之下,Python 3.6 使用
| 函数 | 默认值 | 传统 Windows 文件系统编码 |
|---|---|---|
| open() | mbcs/strict | mbcs/strict |
| os.fsdecode(),os.fsencode() | UTF-8/surrogatepass | mbcs/replace |
| sys.stdin, sys.stdout | UTF-8/surrogateescape | UTF-8/surrogateescape |
| sys.stderr | UTF-8/backslashreplace | UTF-8/backslashreplace |
“传统 Windows 文件系统编码”由 PYTHONLEGACYWINDOWSFSENCODING 环境变量启用。
如果 stdin 和/或 stdout 被重定向到管道,sys.stdin 和/或 sys.stdout 默认使用 mbcs 编码而不是 UTF-8。但在 UTF-8 模式下,sys.stdin 和 sys.stdout 始终使用 UTF-8 编码。
注意
Windows 上没有 POSIX 区域设置。ANSI 代码页用作区域设置编码,并且此代码页从不使用 ASCII 编码。
链接
- bpo-29240: PEP 540 的实现:添加新的 UTF-8 模式
- PEP 538:“强制传统 C 区域设置使用 C.UTF-8”
- PEP 529:“更改 Windows 文件系统编码为 UTF-8”
- PEP 528:“更改 Windows 控制台编码为 UTF-8”
- PEP 383:“系统字符接口中的不可解码字节”
发布历史
- 2017-12: [Python-Dev] PEP 540: 添加新的 UTF-8 模式
- 2017-04: [Python-Dev] PEP 538 & 540 拟议 BDFL 代理更新(假设 *nix 系统边界使用 UTF-8)
- 2017-01: [Python-ideas] PEP 540: 添加新的 UTF-8 模式
- 2017-01: bpo-28180: PEP 538 的实现:强制 C 区域设置为 C.utf-8 (msg284764)
- 2016-08-17: bpo-27781: 更改 Windows 上的 sys.getfilesystemencoding() 为 UTF-8 (msg272916) – Victor 为 PEP 529 (更改 Windows 文件系统编码为 UTF-8) 提议了
-X utf8
版本历史
- 版本 4:在 UTF-8 模式下,
locale.getpreferredencoding()现在返回'UTF-8'。 - 版本 3:UTF-8 模式不再更改
open()的默认错误处理程序(strict),并且已移除严格 UTF-8 模式。 - 版本 2:从头重写 PEP,使其更短、更容易理解。
- 版本 1:第一个版本发布到 python-dev。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0540.rst