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

Python 增强提案

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

由于 EncodingWarningWarning 的子类,因此它们默认显示(如果设置了 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 模块将使用区域编码作为管道的默认编码。

许多测试使用未指定 encodingopen() 来读取 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_encodingPYTHONWARNDEFAULTENCODING=1 来发现此类错误。

当打开以区域编码编码的文本文件时,省略 encoding 参数并不是一个错误,但建议在 Python 3.10 及更高版本中使用 encoding="locale",因为它更明确。

参考实现

https://github.com/python/cpython/pull/19481

讨论

最新的讨论帖子是:https://mail.python.org/archives/list/python-dev@python.org/thread/SFYUP2TWD5JZ5KDLVSTZ44GWKVY4YNCV/

  • 为什么不在 linter 中实现呢?
    • encoding="locale"io.text_encoding() 必须在 Python 中实现。
    • 很难找到包装 open()TextIOWrapper() 的所有函数的调用者(参见 io.text_encoding() 部分)。
  • 许多开发人员不会使用该选项。
    • 有些人会,并将警告报告给他们使用的库,所以即使许多开发人员没有启用它,这个选项也是值得的。
    • 例如,我通过运行 pip install -U pip 发现了 [7][8],通过使用参考实现运行 tox 发现了 [9]。这表明该选项如何用于发现潜在问题。

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0597.rst

最后修改:2025-02-01 08:56:52 GMT