【问题标题】:chcp 65001 codepage results in program termination without any errorchcp 65001 代码页导致程序终止而没有任何错误
【发布时间】:2017-02-05 19:23:23
【问题描述】:

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

我花了几天时间试图解决这个问题,但无济于事。但是今天,我在python websiteMySQL 和 Lua-users 上发现了一个帖子,虽然没有任何解决方案,但有人说chcp 65001 天生就坏了。

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

重现错误

chcp 65001

Python 3.X:

Python 外壳

print('ä')

结果:它只是退出了外壳

然而,这行得通python.exe -c "print('ä')" 还有这个:print('\u00e4')

结果:ä

在Luajit2.0.4

print('ä')

结果:它只是退出了外壳

但是这可行:print('\xc3\xa4')

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

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

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

【问题讨论】:

  • 我安装了多个 Python 版本。我无法在 Windows 10 64 位、Python 3.3.5 64 位或 Python 3.5.2 64 位上重现,但可以在 Python 2.7.12 32 位上重现。它按描述退出,但您说您使用的是 Python 3。也许这是 32 位与 64 位的问题?您使用的是 Windows cmd.exe 控制台还是其他?
  • @MarkTolonen,当使用不是为代码页 65001 设计的控制台(conhost;cmd 只是一个外壳)时,这在所有 Windows 版本中都可以重现。输入单个非 ASCII 字符会导致空读取,Python 的 REPL 和 input 处理为 EOF。问题是 conhost.exe 假设它将其 UTF-16 输入缓冲区编码为每个字符 1 个字节的 ANSI 代码页,因此对于非 ASCII UTF-8,其 WideCharToMultiByte 编码缓冲区太小。读取失败,但它作为 0 字节的“成功”读取返回给客户端,即文件结尾。
  • @eryksun,是的,我知道它坏了,但是,在 64 位 Windows 10 上的 64 位 Python 上,我可以使用国际 IME 输入 cmd.exe print('ä') 和它正确打印出来。所以“在所有 Windows 中可重现”的版本是不准确的,至少对于这个特定的例子是这样。
  • @MarkTolonen,你安装了 pyreadline 吗?
  • @eryksun,是的,这也破解了WriteFile 吗?

标签: python windows unicode cmd codepages


【解决方案1】:

要在 Python 2.7 和 3.x(3.6 之前)的 Windows 控制台中使用 Unicode,请安装并启用 win_unicode_console。这使用宽字符函数ReadConsoleWWriteConsoleW,就像其他支持Unicode 的控制台程序,如cmd.exe 和powershell.exe。对于 Python 3.6,添加了一个新的 io._WindowsConsoleIO 原始 I/O 类。它读取和写入 UTF-8 编码的文本(为了与 Unix 的跨平台兼容性——“获取一个字节”——程序),但在内部它通过与 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 调用会由于缓冲区过小而失败,并且控制台会返回 0 字节的“成功”读取。

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

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

【讨论】:

  • 您的描述很有帮助,但有一部分是错误的:输入了“外来字符”(不在当前键盘布局中的字符)。我在 (stackoverflow.com/a/47843552/9106292) 和 (stackoverflow.com/a/47852866/9106292) 中写下了我对它的了解
  • @IlyaZakharevich,我阅读了您的帖子,但我仍然不能完全确定我在这个答案中有什么错误,除非它只是我将其描述为“非 ASCII”输入而不是具体可用的输入在当前的键盘映射中。
  • «对于非 ASCII 字符,ReadConsoleInputW 返回 Alt+Numpad KEY_EVENT 记录的序列。»这是错误的(或至少不完全正确;)。所描述的行为仅发生在键盘“主平面”中“不存在”的字符上。如果可以使用修饰符组合(无论多么复杂)访问一个字符(但不需要前缀按键!),那么它将被伪造为以这种方式输入。
  • 应用程序会看到带有某些修饰符的某个按键(IIRC,最多伪造Shift-AltGr修饰符;如果需要any扩展修饰符[包括@ 987654347@], AltGr 被替换)。
  • @IlyaZakharevich,谢谢。这就是我以为你的意思。我将研究它来描述控制台在这里所做的事情。之前我只是尝试了一些随机的非 ASCII 字符,并观察到它们使用 Alt+Numpad 序列进行最适合的 OEM 编码,实际的 Unicode 代码点存储在最后一条记录中。
猜你喜欢
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-21
相关资源
最近更新 更多