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

问题
当我想在Python解释器中input Unicode字符时出现问题(为了简单起见,我在示例中使用了一个变音符号,但是我已经第一次遇到了这个波斯语字符)。 每当我用chcp 65001代码页使用python,然后尝试input一个Unicode字符,Python就不会出现任何错误。

我花了好几天的时间试图解决这个问题无济于事。 但是今天,我在Python网站上发现了一个线程,另外一个在MySQL上 ,另一个在Lua用户上,这个突然退出的问题引发了一些问题,尽pipe没有任何解决办法,有人说chcp 65001本质上是坏的。

一劳永逸地知道这个问题是否与chcp-design-related有关或者有可能的解决方法是很好的。

重现错误

chcp 65001

Python 3.X:

Python shell

print('ä')

结果:它只是退出shell

然而 ,这个工程python.exe -c "print('ä')"也是这样的: print('\u00e4')

结果:ä

在Luajit2.0.4

print('ä')

结果:它只是退出shell

然而这个工作: print('\xc3\xa4')

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

  1. 用命令提示符直接输出。
  2. 基于Unicode的,基于hex的angular色作品的等价物。

所以这不是一个Python错误,我们不能直接在Windows命令提示符或其任何包装如Conemu,Cmder(我使用Cmder能够看到和使用Windows中的Unicode字符壳和我没有任何问题,所以这样做)。 它是否正确?

要在Python 2.7和3.x的Windows控制台(3.6之前)中使用Unicode,请安装并启用win_unicode_console 。 与其他支持Unicode的控制台程序(如cmd.exe和powershell.exe)一样,它使用宽字符函数ReadConsoleWWriteConsoleW 。 对于Python 3.6,添加了一个新的io._WindowsConsoleIO原始I / O类。 它读取和写入UTF-8编码的文本(与Unix的跨平台兼容性 – “获取一个字节” – 程序),但在内部它使用宽字符的API转码到UTF-16LE和从UTF-16LE。

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

问题在于,假设一个单字节代码页(例如西方语言环境中的OEM和ANSI代码页(例如437,850,1252)),conhost对其UTF-16输入缓冲区进行编码。 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来解决这个错误。