为什么在使用65001代码页时向CMD写入Unicode时会出现IOErrors?

12

我在Windows 8的CMD中,将代码页设置为65001(chcp 65001)。我正在使用Python 2.7.2(ActivePython 2.7.2.5),并将PYTHONSTARTUP环境变量设置为"bootstrap.py"。

bootstrap.py:

import codecs
codecs.register(
    lambda name: name == 'cp65001' and codecs.lookup('UTF-8') or None
)

这使我可以打印ASCII:

>>> print 'hello'
hello
>>> print u'hello'
hello

但是当我尝试打印一个包含非ASCII字符的Unicode字符串时,我得到的错误对我来说毫无意义。下面我尝试打印一些包含北欧符号的字符串(我在打印之间添加了额外的换行以便阅读):

>>> print u'æøå'
��øåTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory

>>> print u'åndalsnes'
��ndalsnes

>>> print u'åndalsnesæ'
��ndalsnesæTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument

>>> print u'Øst'
��st

>>> print u'uØst'
uØstTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument

>>> print u'ØstÆØÅæøå'
��stÆØÅæøåTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument

>>> print u'_ØstÆØÅæøå'
_ØstÆØÅæøåTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument

正如您所看到的,它并不总是会出现错误(甚至每次出错的错误都不相同),而且北欧符号只有偶尔能够正确显示。

有人能解释这种行为吗,或者至少帮我找出如何正确地在CMD中打印Unicode的方法吗?


2
这是一个噩梦般的情况。在SO和其他地方已经讨论了无数次。例如:http://www.google.com/search?q=print+unicode+windows+console+python - David Heffernan
2
@DavidHeffernan:我已经查看了搜索结果,最接近规范答案的是OP已经在做的事情。对我来说,要么这是一个新变体,要么这个问题从未得到过真正的回答? - Harry Johnston
1
至少在3.3版本中,Windows代码页得到了改进的支持:PyUnicode_EncodeCodePage。后者被codecs.code_page_encode所使用,新的cp65001编解码器使用它来定义encode = functools.partial(codecs.code_page_encode, 65001),解码也是类似的。 - Eryk Sun
2
当前 PRINT_ITEM 操作调用 PyFile_WriteObject,该函数调用 PyObject_Print,最终调用 PyString_Type.tp_print,使用 libc fwrite 向标准输出流 stdout 写入。问题在于一个 bug 会导致 stdout 的文件流的错误标志被设置,尽管没有发生任何错误(因此报告了随机的“错误”),因为 write 返回写入的字符数而不是字节数。您可以通过使用 os.write(sys.stdout.fileno(), s) 进行验证,其中 s 是非 ASCII UTF-8 字符串。 - Eryk Sun
2
在Python 3中,这不是一个问题,因为它实现了自己的缓冲区(_io.BufferedWriter),底层的_io.FileIO对目标文件描述符执行低级别的write操作。 - Eryk Sun
显示剩余5条评论
1个回答

1

Try This :

# -*- coding: utf-8 -*-
    from __future__ import unicode_literals
    print u'æøå'

在交互式Python会话中使用from __future__ import unicode_literals将非常有用。
使用WriteConsoleW可以成功地将Unicode写入控制台,这适用于任何控制台代码页,包括65001。此处的代码here可以实现(它适用于Python 2.x,但无论如何,您都将从C中调用WriteConsoleW)。
我知道WriteConsoleW有一个缺陷,即一次写入超过26608个字符时会失败。通过限制单个调用中传递的数据量,可以轻松解决这个问题。
字体不是Python的问题,但编码是。如果因为某些用户可能没有选择可以显示这些字符的字体而未能输出正确的字符,这是没有意义的。应该重新打开此错误。
(为了完整性,可以使用除Lucida Console和Consolas之外的字体在控制台上显示Unicode,但这需要注册表修改。)希望能对您有所帮助。

我认为WriteConsoleW仅限于UCS-2,也就是说,您不能使用来自补充平面的字符。但在大多数情况下,这不应该是问题。 - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接