PEP 597 – 添加可选的 EncodingWarning
- 作者:
- 稻田直树 <songofacandy at gmail.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2019年6月5日
- Python 版本:
- 3.10
摘要
添加一个新的警告类别 EncodingWarning。当 open() 的 encoding 参数被省略并使用默认的区域特定编码时,将发出此警告。
此警告默认禁用。可以使用新的命令行选项 -X warn_default_encoding 和新的环境变量 PYTHONWARNDEFAULTENCODING 来启用它。
还添加了 encoding 参数的 "locale" 值。它明确指定应使用区域编码,从而抑制警告。
动机
使用默认编码是一个常见错误
使用 macOS 或 Linux 的开发人员可能会忘记默认编码并非总是 UTF-8。
例如,在 setup.py 中使用 long_description = open("README.md").read() 是一个常见错误。如果其 UTF-8 编码的 README.md 文件中至少有一个非 ASCII 字符(例如表情符号、作者姓名、版权符号等),许多 Windows 用户将无法安装此类包。
在 PyPI 上下载量最高的 4000 个包中,有 489 个在其 README 中使用了非 ASCII 字符,有 82 个由于未指定非 ASCII 文件的编码而在非 UTF-8 区域设置中无法从源代码安装。[1]
另一个例子是 logging.basicConfig(filename="log.txt")。有些用户可能期望它默认使用 UTF-8,但实际使用的是区域编码。[2]
即使是 Python 专家也可能认为默认编码是 UTF-8。这会创建只在 Windows 上发生的错误;例如参见 [3]、[4]、[5] 和 [6]。
当省略 encoding 参数时发出警告将有助于发现此类错误。
使用区域特定编码的显式方法
open(filename) 没有明确说明预期使用哪种编码
- 如果假定是 ASCII,这并不是一个错误,但在 Windows 上可能会导致性能下降,特别是在非 Latin-1 区域编码下
- 如果假定是 UTF-8,这可能是一个错误或一个特定于平台的脚本
- 如果假定是区域编码,则行为符合预期(但如果未来版本的 Python 修改了默认值,则可能会改变)
从这个角度来看,open(filename) 不是可读代码。
encoding=locale.getpreferredencoding(False) 可以用来显式指定区域编码,但它太长且容易误用(例如,可能会忘记传递 False 作为其参数)。
本 PEP 提供了一种显式指定区域编码的方法。
准备将默认编码更改为 UTF-8
由于 UTF-8 已成为事实上的标准文本编码,我们将来可能会默认使用它来打开文件。
然而,这样的更改将影响许多应用程序和库。如果我们开始在所有省略 encoding 参数的地方发出 DeprecationWarning,那将太嘈杂和痛苦。
尽管本 PEP 未提议更改默认编码,但它将通过以下方式帮助实现该更改:
- 在我们默认开始发出
DeprecationWarning之前,减少库中省略encoding参数的数量。 - 允许用户传递
encoding="locale"来抑制当前警告和将来添加的任何DeprecationWarning,并确保如果后续 Python 版本更改默认值,也能保持一致的行为,从而确保支持任何 Python 版本 >=3.10。
规范
EncodingWarning
添加一个新的 EncodingWarning 警告类,作为 Warning 的子类。当 encoding 参数被省略并且使用默认的区域特定编码时,将发出此警告。
启用警告的选项
添加了 -X warn_default_encoding 选项和 PYTHONWARNDEFAULTENCODING 环境变量。它们用于启用 EncodingWarning。
sys.flags.warn_default_encoding 也被添加。当 EncodingWarning 启用时,该标志为真。
当该标志设置时,io.TextIOWrapper()、open() 和使用它们的其他模块在省略 encoding 参数时将发出 EncodingWarning。
由于 EncodingWarning 是 Warning 的子类,因此它们默认显示(如果设置了 warn_default_encoding 标志),这与 DeprecationWarning 不同。
encoding="locale"
io.TextIOWrapper 将接受 "locale" 作为 encoding 的有效参数。它与当前的 encoding=None 具有相同的含义,只是当指定 encoding="locale" 时,io.TextIOWrapper 不会发出 EncodingWarning。
io.text_encoding()
io.text_encoding() 是一个辅助函数,用于带有 encoding=None 参数并将其传递给 io.TextIOWrapper() 或 open() 的函数。
一个纯 Python 实现将如下所示
def text_encoding(encoding, stacklevel=1):
"""A helper function to choose the text encoding.
When *encoding* is not None, just return it.
Otherwise, return the default text encoding (i.e. "locale").
This function emits an EncodingWarning if *encoding* is None and
sys.flags.warn_default_encoding is true.
This function can be used in APIs with an encoding=None parameter
that pass it to TextIOWrapper or open.
However, please consider using encoding="utf-8" for new APIs.
"""
if encoding is None:
if sys.flags.warn_default_encoding:
import warnings
warnings.warn(
"'encoding' argument not specified.",
EncodingWarning, stacklevel + 2)
encoding = "locale"
return encoding
例如,pathlib.Path.read_text() 可以这样使用它
def read_text(self, encoding=None, errors=None):
encoding = io.text_encoding(encoding)
with self.open(mode='r', encoding=encoding, errors=errors) as f:
return f.read()
通过使用 io.text_encoding(),EncodingWarning 会针对 read_text() 的调用者发出,而不是针对 read_text() 本身。
受影响的标准库模块
许多标准库模块将受此更改影响。
大多数接受 encoding=None 的 API 将使用上一节中提到的 io.text_encoding()。
在将区域编码作为默认编码是合理的情况下,将使用 encoding="locale"。例如,subprocess 模块将使用区域编码作为管道的默认编码。
许多测试使用未指定 encoding 的 open() 来读取 ASCII 文本文件。它们应该用 encoding="ascii" 重写。
基本原理
选择启用警告
虽然 DeprecationWarning 默认被抑制,但当 encoding 参数被省略时总是发出 DeprecationWarning 会太嘈杂。
嘈杂的警告可能会导致开发人员忽略 DeprecationWarning。
“locale”不是编解码器别名
我们不将“locale”添加为编解码器别名,因为区域设置可以在运行时更改。
此外,当 encoding=None 时,TextIOWrapper 会检查 os.device_encoding()。这种行为无法在编解码器中实现。
向后兼容性
新警告默认不发出,因此本 PEP 100% 向后兼容。
向前兼容性
将 "locale" 作为 encoding 的参数不向前兼容。使用它的代码在早于 3.10 的 Python 上将无法工作,而是会引发 LookupError: unknown encoding: locale。
在开发人员可以放弃对 Python 3.9 的支持之前,EncodingWarning 只能用于查找缺失的 encoding="utf-8" 参数。
如何教授此内容
对于新用户
由于 EncodingWarning 用于编写跨平台代码,因此无需向新用户教授它。
我们只需建议文本文件使用 UTF-8,并在打开它们时使用 encoding="utf-8"。
对于有经验的用户
使用 open(filename) 读取 UTF-8 编码的文本文件是一个常见错误。它可能在 Windows 上无法工作,因为 UTF-8 不是默认编码。
您可以使用 -X warn_default_encoding 或 PYTHONWARNDEFAULTENCODING=1 来发现此类错误。
当打开以区域编码编码的文本文件时,省略 encoding 参数并不是一个错误,但建议在 Python 3.10 及更高版本中使用 encoding="locale",因为它更明确。
参考实现
讨论
最新的讨论帖子是:https://mail.python.org/archives/list/python-dev@python.org/thread/SFYUP2TWD5JZ5KDLVSTZ44GWKVY4YNCV/
参考资料
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0597.rst