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
。
像 cat
或 grep
这样的 Unix 命令行工具以及大多数 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
区域设置设置为 UTF-8 区域设置,如 C.UTF-8
。一个副作用是,非 Python 代码也受区域设置强制的影响。这两个 PEP 相辅相成。
在像 Centos 7 这样的不支持区域设置强制的平台上,POSIX 区域设置仅启用 UTF-8 模式。在这种情况下,Python 代码使用 UTF-8 编码并忽略区域设置编码,而非 Python 代码使用区域设置编码,对于 POSIX 区域设置,该编码通常为 ASCII。
虽然 UTF-8 模式在所有平台上都受支持,并且可以在任何区域设置下启用,但区域设置强制并非所有平台都支持,并且仅限于 POSIX 区域设置。
UTF-8 模式仅在 PYTHONUTF8
环境变量设置为 1
时才会影响 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() | 区域设置/严格 | UTF-8/严格 |
os.fsdecode(),os.fsencode() | 区域设置/surrogateescape | UTF-8/surrogateescape |
sys.stdin,sys.stdout | 区域设置/严格 | UTF-8/surrogateescape |
sys.stderr | 区域设置/backslashreplace | UTF-8/backslashreplace |
相比之下,Python 3.6 使用
功能 | 默认值 | POSIX 区域设置 |
---|---|---|
open() | 区域设置/严格 | 区域设置/严格 |
os.fsdecode(),os.fsencode() | 区域设置/surrogateescape | 区域设置/surrogateescape |
sys.stdin,sys.stdout | 区域设置/严格 | locale/surrogateescape |
sys.stderr | 区域设置/backslashreplace | 区域设置/backslashreplace |
Windows 上的编码和错误处理程序
在 Windows 上,编码和错误处理程序不同
功能 | 默认值 | 传统 Windows 文件系统编码 | UTF-8 模式 |
---|---|---|---|
open() | mbcs/strict | mbcs/strict | UTF-8/严格 |
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
环境变量启用。
如果标准输入和/或标准输出重定向到管道,sys.stdin
和/或 sys.output
默认使用 mbcs
编码而不是 UTF-8。但在 UTF-8 模式下,sys.stdin
和 sys.stdout
始终使用 UTF-8 编码。
注意
Windows 上没有 POSIX 区域设置。ANSI 代码页用作区域设置编码,并且此代码页从不使用 ASCII 编码。
链接
- bpo-29240:PEP 540 的实现:添加新的 UTF-8 模式
- PEP 538:“将传统 C 区域设置强制转换为基于 UTF-8 的区域设置”
- PEP 529:“将 Windows 文件系统编码更改为 UTF-8”
- PEP 528:“将 Windows 控制台编码更改为 UTF-8”
- PEP 383:“系统字符接口中不可解码的字节”
发布历史
- 2017 年 12 月:[Python-Dev] PEP 540:添加新的 UTF-8 模式
- 2017 年 4 月:[Python-Dev] PEP 538 和 540 的拟议 BDFL 代表更新(假设 *nix 系统边界使用 UTF-8)
- 2017 年 1 月:[Python-ideas] PEP 540:添加新的 UTF-8 模式
- 2017 年 1 月:bpo-28180:PEP 538 的实现:将 C 区域设置强制转换为 C.utf-8 (msg284764)
- 2016 年 8 月 17 日:bpo-27781:将 Windows 上的 sys.getfilesystemencoding() 更改为 UTF-8 (msg272916) – Victor 建议使用
-X utf8
用于 PEP 529(将 Windows 文件系统编码更改为 UTF-8)
版本历史
- 版本 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