问题 帮助我理解为什么Unicode有时只能用于Python


这是一个小程序:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

print('abcd kΩ  °C √Hz µF ü  ')  
print(u'abcd kΩ  °C √Hz µF ü  ')

在Ubuntu,Gnome终端上,IPython做了我所期望的:

In [6]: run Unicodetest.py
abcd kΩ  °C √Hz µF ü  
abcd kΩ  °C √Hz µF ü  

如果我输入命令,我会得到相同的输出 trypython.org

codepad.org另一方面,为第二个命令产生错误:

abcd kΩ  °C √Hz µF ü  
Traceback (most recent call last):
  Line 6, in <module>
    print(u'abcd kΩ  °C √Hz µF ü  ')
UnicodeEncodeError: 'ascii' codec can't encode character u'\u03a9' in position 6: ordinal not in range(128)

相反,Windows上的IDLE会破坏第一个命令的输出,但不会抱怨第二个命令:

>>>
abcd kΠ☠ °C √Hz µF ü ☃ â¥
abcd kΩ  °C √Hz µF ü  

在Windows命令提示符下或通过Python(x,y)的Console2版本的IPython都会破坏第一个输出并抱怨第二个输出:

In [9]: run Unicodetest.py
abcd kΩ ☠ °C √Hz µF ü ☃ ♥
ERROR: An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (15, 0))

---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)

Desktop\Unicodetest.py in <module>()
      4 print('abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
      5
----> 6 print(u'abcd kΩ ☠ °C √Hz µF ü ☃ ♥')
      7
      8

C:\Python27\lib\encodings\cp437.pyc in encode(self, input, errors)
     10
     11     def encode(self,input,errors='strict'):
---> 12         return codecs.charmap_encode(input,errors,encoding_map)
     13
     14     def decode(self,input,errors='strict'):

UnicodeEncodeError: 'charmap' codec can't encode character u'\u2620' in position 8: character maps to <undefined>
WARNING: Failure executing file: <Unicodetest.py>

Python(x,y)的Spyder中的IPython也是如此,但不同:

In [8]: run Unicodetest.py
abcd kΠ☠ °C √Hz µF ü ☃ â¥
------------------------------------------------------------
Traceback (most recent call last):
  File "Unicodetest.py", line 6, in <module>
    print(u'abcd kΠ☠ °C √Hz µF ü ☃ â¥')
  File "C:\Python26\lib\encodings\cp1252.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_table)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u03a9' in position 6: character maps to <undefined>

WARNING: Failure executing file: <Unicodetest.py>

(在 sitecustomize.py,Spyder设定了自己的 SPYDER_ENCODING 基于locale模块的编码,即 cp1252 对于Windows 7.)

是什么赋予了?我的命令之一是错的吗?为什么一个平台在某些平台上运行而另一个平台在其他平台上运行?如何在不崩溃或搞砸的情况下始终如一地打印Unicode字符?

是否有一个Windows的备用终端,其行为类似于Ubuntu中的终端?似乎TCC-LE,Console2,Git Bash,PyCmd等都只是cmd.exe的包装而不是替换。有没有办法在IDLE使用的接口内运行IPython?


12513
2018-04-17 18:15


起源

在IPython unicode不幸被打破。我们应该为下一个版本0.11修复它,所以它就像在原始Python解释器上键入一样。 - Thomas K
查 这个 出。 - Soorena


答案:


Python(以及大多数其他语言)中的I / O基于 字节。当你写一个字节串(str 在2.x中, bytes 在3.x)到文件中,字节简单地按原样写入。编写Unicode字符串时(unicode 在2.x中, str 在3.x)到文件,数据需要 编码 到一个字节序列。

有关这种区别的进一步解释,请参阅 深入Python 3 字符串章节

print('abcd kΩ  °C √Hz µF ü  ')

这里,字符串是一个字节串。因为源文件的编码是UTF-8,所以字节是

'abcd k\xce\xa9 \xe2\x98\xa0 \xc2\xb0C \xe2\x88\x9aHz \xc2\xb5F \xc3\xbc \xe2\x98\x83 \xe2\x99\xa5'

print 语句将这些字节按原样写入控制台。但Windows控制台将字节字符串解释为在“OEM”代码页中编码,在美国是这样 437。所以你在屏幕上看到的字符串是

abcd kΩ ☠ °C √Hz µF ü ☃ ♥

在您的Ubuntu系统上,这不会导致问题,因为默认控制台编码是UTF-8,因此您没有源文件编码和控制台编码之间的差异。

print(u'abcd kΩ  °C √Hz µF ü  ')

打印Unicode字符串时,必须获取字符串 编码 成字节。但它只有在您拥有支持这些字符的编码时才有效。而你没有。

  • 默认的IBM437编码缺少字符
  • 窗口1252 Spyder使用的编码缺少字符 Ω√

因此,在这两种情况下,您都会遇到尝试打印字符串的UnicodeEncodeError。

是什么赋予了?

Windows和Linux采用了截然不同的方法来支持Unicode。

最初,它们的工作方式大致相同:每个语言环境都有自己的语言特定 char基于编码(Windows中的“ANSI代码页”)。西方语言使用ISO-8859-1或windows-1252,俄语使用KOI8-R或windows-1251等。

当Windows NT添加对Unicode的支持时(在假设Unicode将使用16位字符的早期阶段),它通过创建使用的API的并行版本来实现 wchar_t 代替 char。例如, 消息框 功能分为两个功能:

int MessageBoxA(HWND hWnd, const char* lpText, const char* lpCaption, unsigned int uType);
int MessageBoxW(HWND hWnd, const wchar_t* lpText, const wchar_t* lpCaption, unsigned int uType);

“W”功能是“真实的”功能。 “A”函数用于向后兼容基于DOS的Windows,并且主要只是将其字符串参数转换为UTF-16,然后调用相应的“W”函数。

在Unix世界(特别是Plan 9)中,编写一个全新版本的POSIX API被认为是不切实际的,因此以不同的方式处理Unicode支持。 CJK语言环境中对多字节编码的现有支持用于实现现在称为UTF-8的新编码。

在编写支持Unicode的跨平台代码时,类Unix系统上的UTF-8和Windows上的UTF-16的偏好是一个巨大的痛苦。 Python试图将其隐藏在程序员之外,但是 打印到控制台 是乔尔的“漏洞抽象”之一。


10
2018-04-18 20:34



这非常有帮助,谢谢。我仍然想知道是否有办法在Windows中的IPython中进行“打印”工作,无论是在内置Windows控制台还是在其他一些第三方控制台(如果存在这样的东西)。如果无法显示特殊字符,我至少要打印“?”或者没有崩溃的东西。 - endolith
@christian:是的,Notepad ++可以保存为UTF-8,但这似乎不是问题所在。问题是文件的编码与stdout的编码不匹配。 - dan04
如果模块正在输出类似的字符串 u'G\xc3\xb6teborg, Sweden',这不正确吗?它应该是 u'G\xf6teborg, Sweden'或者,在编码为UTF-8之后, 'G\xc3\xb6teborg, Sweden' 没有 u。 - endolith
我相信它是,而解决方案是 u'G\xc3\xb6teborg, Sweden'.encode('raw_unicode_escape') → 'G\xc3\xb6teborg, Sweden' - endolith


有两个可能的原因:

  • Unicode的编码 print。你不能输出原始的Unicode,所以 print 需要弄清楚如何将其转换为控制台预期的字节流(它使用 sys.stdout.encoding AFAIK),它带给我们
  • 控制台支持。 Python不控制你的终端,所以如果你的终端需要别的东西,它会吐出UTF-8,你就会得到错误的输出。

2
2018-04-17 18:24





你的问题是你的程序期望和输出UTF-8字符,但是网络上的控制台和各种python跑者使用其他代码页。 没有修改就无法对在所有编码中起作用的特殊字符进行编码。 但是,如果您选择使用UTF-8 到处,你应该是安全的。

我认为Windows中的任何终端都可以 - 所以不要因为这个而烦恼切换默认的终端(cmd.exe)。相反,也要将终端的编码更改为UTF-8,以匹配python脚本的编码。

不幸的是,我从来没有找到过将代码页设置为UTF-8的方法,因此每次打开新的命令提示符时都必须这样做。但它是通过一个简单的命令完成的,所以它只有一半 - 你改变了编码 切换代码页

>chcp 65001
Current codepage is now 65001

请注意,您必须使用其中一种标准字体才能工作。网络上的大多数消息来源似乎都建议使用Lucida Console。


0
2018-04-17 18:24



现在我尝试的每个命令都失败了 LookupError: unknown encoding: cp65001 由于 line = raw_input_original(prompt).decode(self.stdin_encoding) 在 C:\Python27\lib\site-packages\IPython\iplib.pyc - endolith
不幸的是,有许多问题 chcp 65001。 Microsoft C运行时和默认Windows控制台旨在使用特定于语言环境的代码页;当其他所有人都转向使用UTF-8时,这真是一种耻辱。 - bobince


从Python到Windows控制台的Unicode输出不起作用。无法说服Python发出需要宽字符和UCS2的本机Windows编码。


0
2018-04-17 18:41



我很高兴在这里投票,因为这意味着我错了,最终能够在Windows控制台中获得良好的unicode支持。现在我只是在等待如何做到这一点的细节。 - David Heffernan
嗯...你甚至不能用标准的C运行时输出UCS-2,它总是使用特定于语言环境的ASCII超集代码页(从不使用任何类型的UTF)。有一个单独的Win32专用接口,可用于输出Unicode内容, WriteConsoleW,但是你必须决定输出字节或字符是你的意思,这可能取决于平台,或者你的IO流是否被重定向到文件。这一切都有点混乱。 - bobince
@bobince结果证明这是Michael Kaplan所揭示的一个神话: blogs.msdn.com/b/michkap/archive/2008/03/18/8306597.aspx  唱歌为 _O_U16TEXT! - David Heffernan
关键词是 标准 C运行时。 Kaplan的示例使用Windows特定的功能。 - dan04
@ dan04向下滚动到底部,你会明白我的意思。此外,没有标准的C运行时。你的意思是MS C运行时。 - David Heffernan


@ dan04:问题是文件的编码与stdout的编码不匹配是对的。然而,解决该问题的一种方法是更改​​文件的编码。所以在Windows Notepad ++上可以用来保存带有UTF-8字符编码的代码。

另一种选择是GNU重新编码。


0
2018-04-19 09:51