帮我理解为什么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的命令提示符下运行IPython,或者通过Python(x,y)的Console2版本,都会把第一个输出搞乱,并且对第二个命令报错:
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
,在Windows 7上是cp1252
。)
这是怎么回事?我的命令有错吗?为什么在某些平台上一个命令能工作,而在其他平台上另一个命令能工作?我该如何稳定地打印Unicode字符,而不出现崩溃或混乱的情况?
有没有一种Windows的终端,能像Ubuntu中的那样工作?看起来TCC-LE、Console2、Git Bash、PyCmd等都只是cmd.exe的包装,而不是替代品。有没有办法在IDLE使用的界面中运行IPython?
5 个回答
从Python输出Unicode到Windows控制台就是不太好使。Python无法被说服去使用Windows本身的编码方式,而Windows的编码是需要宽字符和UCS2格式的。
可能有两个原因:
- Unicode的编码问题。你不能直接输出原始的Unicode,所以
print
需要想办法把它转换成控制台所需要的字节流(据我所知,它使用的是sys.stdout.encoding
),这就引出了第二个原因。 - 控制台的支持问题。Python并不能控制你的终端,所以如果终端输出的是UTF-8格式,而你的终端又期待其他格式,就会出现乱码。
在Python(以及大多数其他编程语言)中,输入输出是基于字节的。当你把一个字节字符串(在2.x中是str
,在3.x中是bytes
)写入文件时,这些字节会直接写入文件。而当你写入一个Unicode字符串(在2.x中是unicode
,在3.x中是str
)时,数据需要被编码成字节序列。
如果想更深入了解这个区别,可以查看《深入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编码缺少字符
☠☃♥
- Spyder使用的windows-1252编码缺少字符
Ω☠√☃♥
。
所以在这两种情况下,你在打印字符串时都会遇到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
。例如,MessageBox函数被分成了两个函数:
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的新编码。
在类Unix系统上偏向使用UTF-8,而在Windows上偏向使用UTF-16,这在编写支持Unicode的跨平台代码时是个大麻烦。Python试图将这一点隐藏起来,但打印到控制台是Joel所说的“泄漏抽象”的一个例子。