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

时间:2016-09-28 02:03:53

标签: python windows unicode cmd codepages

问题
当我想在Python解释器中输入 Unicode字符时出现问题(为简单起见,我在示例中使用了a-umlaut,但我首先遇到了这个用于Farsi字符)。每当我将python与chcp 65001代码页一起使用,然后尝试输入一个Unicode字符时,Python就会退出而没有任何错误。

我花了好几天试图解决这个问题无济于事。但是今天,我在python website上发现了一个帖子,另一个在Lua用户上找到了另一个帖子,虽然没有任何解决方案,但有些人说chcp 65001是天生就是破碎了。

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

重现错误

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的,基于十六进制的角色等效角色。
  3. 因此   这不是Python错误,我们不能在Windows命令提示符中的CLI程序中直接使用Unicode字符,或者像Conemu,Cmder这样的任何Wrapper(我使用Cmder能够看到)并在Windows shell中使用Unicode字符,我没有任何问题)。这是对的吗?

1 个答案:

答案 0 :(得分:7)

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

您在非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的缓冲编写器,导致重复写入它认为剩余的未写入字节。此问题已在Windows 8中修复,作为重写内部控制台API以使用ConDrv设备而不是LPC端口的一部分。较旧版本的Windows可以使用ConEmu或ANSICON来解决此错误。