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

Python 增强提案

PEP 528 – 将 Windows 控制台编码更改为 UTF-8

作者:
Steve Dower <steve.dower at python.org>
状态:
最终版
类型:
标准跟踪
创建日期:
2016年8月27日
Python 版本:
3.6
发布历史:
2016年9月1日,2016年9月4日
决议:
Python-Dev 消息

目录

摘要

历史上,Python 使用 ANSI API 通过 C 运行时函数与 Windows 操作系统交互。然而,这些 API 长期以来一直被弃用,取而代之的是 UTF-16 API。在操作系统内部,所有文本都表示为 UTF-16,而 ANSI API 使用活动代码页执行编码和解码。

本 PEP 提议将 Windows 上的默认标准流实现更改为使用 Unicode API。这将允许用户在默认 Windows 控制台上打印和输入全范围的 Unicode 字符。这也需要对词法分析器如何从 readline 钩子解析文本进行细微更改。

具体更改

添加 _io.WindowsConsoleIO

目前,_io.FileIO 的实例用于包装表示标准输入、输出和错误的 文件描述符。我们添加了一个新的类(用 C 实现)_io.WindowsConsoleIO,它作为使用 Windows 控制台函数的原始 IO 对象,特别是 ReadConsoleWWriteConsoleW

当旧版模式标志未生效时,当通过文件描述符打开标准流且该流是控制台缓冲区而不是重定向文件时,将使用此类别。否则,将像现在一样使用 _io.FileIO

这是一个原始(字节)IO 类,需要以 utf-8 编码的文本,该文本将解码为 utf-16-le 并传递给 Windows API。类似地,从该类读取的字节将由操作系统以 utf-16-le 形式提供,并在返回给 Python 时转换为 utf-8。

为了保持与绕过 TextIOWrapper 并直接将 ASCII 字节写入标准流的代码(例如,Twisted 的 process_stdinreader.py)的兼容性,需要使用与 ASCII 兼容的编码。假定标准流具有 ASCII 以外的特定编码的代码可能会中断。

添加 _PyOS_WindowsConsoleReadline

为了允许在交互式提示符下输入 Unicode,需要一个新的 readline 钩子。现有的 PyOS_StdioReadline 函数将在从作为控制台缓冲区的文件描述符读取且旧版模式标志未生效时委托给新的 _PyOS_WindowsConsoleReadline 函数(逻辑应与上述相同)。

由于 readline 接口需要返回一个没有嵌入空字符的 8 位编码字符串,因此 _PyOS_WindowsConsoleReadline 函数将从操作系统读取的 utf-16-le 转码为 utf-8。

目前从 sys.stdin 获取编码的函数 PyRun_InteractiveOneObject 将选择 utf-8,除非旧版模式标志生效。这可能需要 readline 钩子更改其编码为 utf-8,或者需要旧版模式才能正常工作。

添加旧版模式

使用环境变量 PYTHONLEGACYWINDOWSSTDIO 启动 Python 将启用旧版模式标志,该标志将完全恢复以前的行为。

替代方法

win_unicode_console 包是更改控制台默认行为的纯 Python 替代方案。它使用纯 Python 代码实现了与此处描述的基本相同的修改。

可能导致代码中断

以下代码模式可能由于此更改而中断或表现出不同的行为。所有这些代码示例都需要明确选择使用原始文件对象,而不是更方便的包装器,这样可以防止任何可见的更改。

假设 stdin/stdout 编码

假设 sys.stdin.buffersys.stdout.buffer 所需的编码是 'mbcs' 或更具体的编码的代码目前可能偶然工作,但在此更改下可能会遇到问题。例如

>>> sys.stdout.buffer.write(text.encode('mbcs'))
>>> r = sys.stdin.buffer.read(16).decode('cp437')

要更正此代码,应使用 TextIOWrapper 上指定的编码,无论是隐式还是显式

>>> # Fix 1: Use wrapper correctly
>>> sys.stdout.write(text)
>>> r = sys.stdin.read(16)

>>> # Fix 2: Use encoding explicitly
>>> sys.stdout.buffer.write(text.encode(sys.stdout.encoding))
>>> r = sys.stdin.buffer.read(16).decode(sys.stdin.encoding)

不正确地使用原始对象

使用原始 IO 对象且未正确处理部分读写操作的代码可能会受到影响。这对于读取尤其重要,因为读取的字符数永远不会超过允许的字节数的四分之一,因为没有可行的方法可以防止输入编码为长得多的 utf-8 字符串

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(15)
abcdefghijklm
b'abc'
# data contains at most 3 characters, and never more than 12 bytes
# error, as "defghijklm\r\n" is passed to the interactive prompt

为了纠正此代码,应使用缓冲读/写器,或者调用者应继续读取直到其缓冲区已满

>>> # Fix 1: Use the buffered reader/writer
>>> stdin = sys.stdin.buffer
>>> data = stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

>>> # Fix 2: Loop until enough bytes have been read
>>> raw_stdin = sys.stdin.buffer.raw
>>> b = b''
>>> while len(b) < 15:
...     b += raw_stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

使用小缓冲区处理原始对象

使用原始 IO 对象并尝试读取少于四个字符的代码现在将收到错误。因为任何单个字符在 utf-8 表示中可能需要多达四个字节,所以请求必须失败

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: must read at least 4 bytes

唯一的解决方法是传递更大的缓冲区

>>> # Fix: Request at least four bytes
>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(4)
a
b'a'
>>> >>>

(多余的 >>> 是由于输入缓冲区中保留的换行符造成的,在这种情况下是预期的。)


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

上次修改: 2025-02-01 08:59:27 GMT