来自Python 2.6 shell:
>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>>
在打印语句后面,我预期要么输出一些无意义的字符,要么出现错误,因为"é"字符不属于ASCII码表,而且我没有指定编码方式。我猜我不理解ASCII作为默认编码的含义。
编辑
来自Python 2.6 shell:
>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>>
在打印语句后面,我预期要么输出一些无意义的字符,要么出现错误,因为"é"字符不属于ASCII码表,而且我没有指定编码方式。我猜我不理解ASCII作为默认编码的含义。
编辑
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
让我们暂时退出Python shell,并使用一些虚假编码来设置bash环境:
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
太好了!
如果你现在尝试在ascii之外输出一些unicode字符,你应该会得到一个很好的错误消息。
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
让我们退出Python并关闭bash shell。
现在,我们将观察Python输出字符串后会发生什么。为此,我们首先要在图形终端中启动一个bash shell(我使用Gnome Terminal),并将终端设置为使用ISO-8859-1(也称为latin-1)解码输出(图形终端通常在其下拉菜单中有一个“设置字符编码”的选项)。请注意,这不会更改实际的shell环境编码,它只会更改终端本身解码所给定输出的方式,有点像Web浏览器。因此,您可以独立于shell环境更改终端的编码。然后,让我们从shell启动Python,并验证sys.stdout.encoding是否设置为shell环境的编码(对我来说是UTF-8):
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>
(1) Python将二进制字符串原样输出,终端接收并尝试将其值与latin-1字符映射匹配。在latin-1中,0xe9或233产生字符“é”,因此这就是终端显示的内容。
(2) Python尝试使用当前设置在sys.stdout.encoding中的任何方案隐式地编码Unicode字符串,在这种情况下为“UTF-8”。 UTF-8编码后,生成的二进制字符串是'\xc3\xa9'(见下面的解释)。终端接收该流,尝试使用latin-1解码0xc3a9,但是latin-1的范围从0到255,因此仅逐字节解码流。 0xc3a9长达2个字节,因此latin-1解码器将其解释为0xc3(195)和0xa9(169),并产生2个字符:Ã和©。
(3) Python使用latin-1方案对Unicode代码点u'\ xe9'(233)进行编码。结果发现,latin-1代码点范围为0-255,并且在该范围内指向与Unicode相同的确切字符。因此,在该范围内的Unicode代码点将在latin-1中编码时产生相同的值。因此,在latin-1中编码的u'\ xe9'(233)也会产生二进制字符串'\ xe9'。终端接收该值并尝试在latin-1字符映射上进行匹配。就像情况(1)一样,它产生“é”,这就是所显示的内容。
现在让我们从下拉菜单中更改终端的编码设置为UTF-8(就像更改Web浏览器的编码设置一样)。不需要停止Python或重新启动shell。终端的编码现在与Python相匹配。让我们再次尝试打印:
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
0xxx xxxx (in binary)
例如,“B”的Unicode代码点是“0x42”,或者用二进制表示为0100 0010(与ASCII相同)。在UTF-8中编码后变为:
0xxx xxxx <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010 <-- Unicode code point 0x42
0100 0010 <-- UTF-8 encoded (exactly the same)
Unicode代码点超过127(非ASCII)的UTF-8编码:
110x xxxx 10xx xxxx <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx <-- (from 2048 to 65535)
例如,“é”的Unicode代码点为0xe9(233)。
1110 1001 <-- 0xe9
110x xxxx 10xx xxxx <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001 <-- 0xe9
1100 0011 1010 1001 <-- 'é' after UTF-8 encoding
C 3 A 9
当Unicode字符被打印到标准输出时,会使用sys.stdout.encoding
。假定非Unicode字符在sys.stdout.encoding
中,并且将其直接发送到终端。在我的系统上(Python 2):
>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437'))
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9' # Byte is just sent to terminal and assumed to be CP437.
Θ
sys.getdefaultencoding()
只有在Python没有其他选项时才会使用。
请注意,Python 3.6或更高版本会忽略Windows上的编码并使用Unicode API将Unicode写入终端。如果字体支持它,则不会出现UnicodeEncodeError警告,并且正确的字符将显示出来。即使字体不支持它,字符仍然可以从终端剪切和粘贴到具有支持字体的应用程序中,其结果也将是正确的。升级吧!
Python REPL会尝试从您的环境中选择要使用的编码方式。如果它发现了一些合理的内容,那么所有的操作都可以正常工作。但当它无法确定正在发生什么时,就会出现错误。
>>> print sys.stdout.encoding
UTF-8
您通过输入显式的Unicode字符串指定了编码。与不使用u
前缀的结果进行比较。
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'
>>>
\xe9
时,Python会默认使用Ascii编码,因此打印出来的内容可能为空。对我来说它有效:
import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
unicode
时,它会使用<file>.encoding
进行编码。
encoding
时,unicode
会隐式转换为str
(因为该编解码器为sys.getdefaultencoding()
,即ascii
,任何国际字符都会导致UnicodeEncodeError
)encoding
是从环境中推断出来的。它通常针对tty
流进行设置(从终端的区域设置中),但可能不适用于管道
print u'\xe9'
很可能会成功,如果重定向,则会失败。解决方案是在print
之前使用所需的编码对字符串进行encode()
。str
时,字节将按原样发送到流中。终端显示的图形取决于其区域设置。
'\xe9'
将不会打印出é
。它将打印一个替换字符(通常是问号),因为\xe9
不是有效的UTF-8序列(缺少应该跟随该前导字节的两个字节)。它肯定不会被解释为Latin-1。 - Martijn Pieters\xe9
以打印é
时。 - Michael Ekoka