chcp 65001 代码页导致程序终止而没有任何错误

chcp 65001 codepage results in program termination without any error

问题
当我想在 Python 解释器中 input Unicode 字符时出现问题(为简单起见,我在示例中使用了变音符号,但我第一次遇到波斯字符) .每当我使用 python 和 chcp 65001 代码页然后尝试输入一个 Unicode 字符时,Python 退出时没有任何错误。

我花了几天时间试图解决这个问题,但无济于事。但是今天,我在 python website, another on MySQL 和 Lua 上发现了一个帖子-用户提出了关于这个突然退出的问题,尽管没有任何解决方案,而且有人说 chcp 65001 本身就坏了。

最好一劳永逸地知道这个问题是与 chcp 设计相关还是有可能的解决方法。

重现错误

chcp 65001

Python 3.X:

Python shell

print('ä')

结果:它刚刚退出 shell

但是,这有效python.exe -c "print('ä')" 还有这个:print('\u00e4')

结果:ä

in Luajit2.0.4

print('ä')

结果:它刚刚退出 shell

然而这有效:print('\xc3\xa4')

到目前为止我已经得出了这个观察结果:

  1. 使用命令提示符直接输出有效。
  2. 基于 Unicode,基于十六进制的字符等效项。

所以 这不是 Python 错误 我们不能在 CLI 程序中直接在 Windows 命令提示符或它的任何包装器(如 Conemu)中使用 Unicode 字符, Cmder(我正在使用 Cmder 来查看和使用 Windows shell 中的 Unicode 字符,我这样做没有任何问题)。这是正确的吗?

要在 Python 2.7 和 3.x(3.6 之前)的 Windows 控制台中使用 Unicode,安装并启用 win_unicode_console. This uses the wide-character functions ReadConsoleW and WriteConsoleW,就像其他支持 Unicode 的一样控制台程序,例如 cmd.exe 和 powershell.exe。对于 Python 3.6,添加了一个新的 io._WindowsConsoleIO raw I/O class。它读取和写入 UTF-8 编码的文本(为了与 Unix 的跨平台兼容性 -- "get a byte" -- 程序),但在内部它通过与 UTF-16LE 之间的代码转换使用宽字符 API .

您遇到的非 ASCII 输入问题在所有 Windows 版本(包括 Windows 10)的控制台中都可以重现。控制台主机进程,即 conhost.exe,不是为 UTF-8(代码页 65001)设计的,也没有更新以一致地支持它。特别是,非 ASCII 输入会导致空读取。这反过来会导致 Python 的 REPL 退出并内置 input 引发 EOFError

问题是 conhost 假定单字节代码页对其 UTF-16 输入缓冲区进行编码,例如西方语言环境中的 OEM 和 ANSI 代码页(例如 437、850、1252)。 UTF-8 是一种多字节编码,其中非 ASCII 字符被编码为 2 到 4 个字节。要处理 UTF-8,它需要在 M / 4 个字符的多次迭代中进行编码,其中 M 是 N 字节缓冲区中可用的剩余字节。相反,它假定读取 N 个字节的请求是读取 N 个字符的请求。然后,如果输入有一个或多个非 ASCII 字符,内部 WideCharToMultiByte 调用会因缓冲区过小而失败,控制台 return 会 'successful' 读取 0 个字节。

如果安装了 pyreadline 模块,您可能无法在 Python 3.5 中准确观察到此问题。 Python 3.5 自动尝试导入 readline。在 pyreadline 的情况下,输入是通过宽字符函数 ReadConsoleInputW 读取的。这是读取控制台输入记录的低级函数。原则上它应该有效,但实际上输入 print('ä') 会被 REPL 读取为 print('')。对于非 ASCII 字符,ReadConsoleInputW return 是一系列 Alt+Numpad KEY_EVENT 记录。该序列是有损 OEM 编码,除了最后一条记录外,可以忽略它,该记录在 UnicodeChar 字段中具有输入字符。显然 pyreadline 忽略了整个序列。

在 Windows8 之前,使用代码页 65001 的输出也已损坏。它会打印出与非 ASCII 字符数量成比例的垃圾文本。在这种情况下,问题是 WriteFileWriteConsoleA 错误地 return 写入屏幕缓冲区的 UTF-16 代码数而不是 UTF-8 字节数。这会混淆 Python 的缓冲写入器,导致重复写入它认为是剩余未写入字节的内容。作为重写内部控制台 API 以使用 ConDrv 设备而不是 LPC 端口的一部分,此问题已在 Windows 8 中得到修复。 Windows 的旧版本可以使用 ConEmu 或 ANSICON 解决此错误。