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

Python 增强提案

PEP 597 – 添加可选的 EncodingWarning

作者:
Inada Naoki <songofacandy at gmail.com>
状态:
最终
类型:
标准跟踪
创建:
2019-06-05
Python 版本:
3.10

目录

摘要

添加一个新的警告类别 EncodingWarning。当 open()encoding 参数被省略,并且使用默认的特定于区域设置的编码时,会发出此警告。

默认情况下,警告被禁用。可以使用新的 -X warn_default_encoding 命令行选项和新的 PYTHONWARNDEFAULTENCODING 环境变量来启用它。

还添加了 "locale" 作为 encoding 的参数值。它显式地指定应使用区域设置编码,从而抑制警告。

动机

使用默认编码是一个常见的错误

使用 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 具有相同的含义,只是 io.TextIOWrapper 在指定 encoding="locale" 时不会发出 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 模块将使用区域设置编码作为管道默认编码。

许多测试使用 open() 而不指定 encoding 来读取 ASCII 文本文件。它们应该用 encoding="ascii" 重写。

基本原理

选择性警告

虽然 DeprecationWarning 默认情况下被抑制,但当省略 encoding 参数时总是发出 DeprecationWarning 会过于嘈杂。

嘈杂的警告可能会导致开发人员忽略 DeprecationWarning

“locale”不是一个编解码器别名

我们没有将 “locale” 添加为编解码器别名,因为区域设置可以在运行时更改。

此外,TextIOWrapperencoding=None 时检查 os.device_encoding()。这种行为无法在编解码器中实现。

向后兼容性

新的警告默认情况下不会发出,因此本 PEP 与向后兼容。

向前兼容性

"locale" 作为参数传递给 encoding 并非向前兼容。使用它的代码在 Python 3.10 之前的版本中将无法工作,并且会引发 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/

  • 为什么不在代码规范工具中实现这个功能呢?
    • 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

最后修改时间:2023-09-09 17:39:29 GMT