为什么Python在默认编码是ASCII时打印unicode字符?

从Python 2.6shell:

>>> import sys >>> print sys.getdefaultencoding() ascii >>> print u'\xe9' é >>> 

我打算在打印语句后有一些乱码或错误,因为“é”字符不是ASCII的一部分,我没有指定编码。 我想我不明白是什么ASCII是默认的编码手段。

编辑

我将编辑移到Answers部分,并按build议接受。

由于各种答复中的点点滴滴,我想我们可以解释一下。

通过尝试打印unicodestringu'\ xe9',Python隐式地尝试使用当前存储在sys.stdout.encoding中的编码scheme对该string进行编码。 Python实际上从启动的环境中选取了这个设置。 如果它无法从环境中find正确的编码,只有这样它才会恢复到默认的 ASCII。

例如,我使用一个编码默认为UTF-8的bash shell。 如果我从它启动Python,它会启动并使用该设置:

 $ 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 shell,并确认它确实恢复为默认的ascii编码。

 $ 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输出string后会发生什么。 为此,我们首先在一个graphicsterminal(我使用Gnometerminal)中启动一个bash shell,然后我们将terminal设置为使用ISO-8859-1(也就是latin-1)解码输出(graphicsterminal通常有一个设置字符在其中一个下拉菜单中进行编码 )。 请注意,这不会改变实际的shell环境的编码,它只会改变terminal本身将解码输出的方式,有点像Web浏览器。 因此,您可以独立于shell的环境更改terminal的编码。 然后让我们从shell启动Python,并validationsys.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按原样输出二进制串,terminal接收它并尝试将其值与latin-1字符映射匹配。 在拉丁语-1中,0xe9或233产生字符“é”,所以这就是terminal显示的内容。

(2)python尝试用当前在sys.stdout.encoding中设置的任何scheme来隐式编码Unicodestring,在这种情况下它是“UTF-8”。 经过UTF-8编码后,得到的二进制string是'\ xc3 \ xa9'(见后面的解释)。 terminal接收到这样的数据stream,并尝试使用latin-1对0xc3a9进行解码,但latin-1从0到255,因此,一次只解码数据stream1个字节。 0xc3a9是2个字节长,latin-1解码器因此将其解释为0xc3(195)和0xa9(169),并产生2个字符:Ã和©。

(3)Python使用拉丁-1scheme编码unicode代码点u'\ xe9'(233)。 原来,latin-1代码点的范围是0-255,并指向与该范围内的Unicode完全相同的字符。 因此,用latin-1编码时,该范围内的Unicode代码点将产生相同的值。 所以用'latin-1'编码的u'\ xe9'(233)也会产生二进制string'\ xe9'。 terminal接收该值并尝试在拉丁字母1映射上匹配它。 就像情况(1)一样,它产生“é”,这就是显示的内容。

现在让我们从下拉菜单中将terminal的编码设置更改为UTF-8(就像您要更改网页浏览器的编码设置一样)。 不需要停止Python或重新启动shell。 terminal的编码现在匹配Python的。 我们再试一次打印:

 >>> print '\xe9' # (4) >>> print u'\xe9' # (5) é >>> print u'\xe9'.encode('latin-1') # (6) >>> 

(4)python按原样输出一个二进制string。 terminal尝试用UTF-8解码该stream。 但是UTF-8不理解值0xe9(见后面的解释),因此无法将其转换为unicode代码点。 没有find代码点,没有打印字符。

(5)python尝试隐式地将Unicodestring与sys.stdout.encoding中的string进行编码。 仍然是“UTF-8”。 产生的二进制string是'\ xc3 \ xa9'。 terminal接收stream并尝试使用UTF-8解码0xc3a9。 它返回代码值0xe9(233),在Unicode字符映射上指向符号“é”。 terminal显示“é”。

(6)python使用latin-1编码unicodestring,它会产生一个具有相同值'\ xe9'的二进制string。 再次,对于terminal来说,这与案例(4)几乎相同。

结论: – Python将非Unicodestring输出为原始数据,而不考虑其默认编码。 如果当前的编码与数据匹配,terminal恰好显示它们。 – Python使用sys.stdout.encoding中指定的scheme编码后输出Unicodestring。 – Python从shell的环境中获取设置。 – terminal根据自己的编码设置显示输出。 – terminal的编码是独立于shell的。


unicode,UTF-8和latin-1的更多细节:

Unicode基本上是一个字符表,其中一些键(代码点)通常被指定为指向一些符号。 例如按照惯例,已经确定键0xe9(233)是指向符号“é”的值。 ASCII和Unicode使用相同的代码点,从0到127,latin-1和Unicode从0到255.也就是说,0x41指向'A',ASCII,latin-1和Unicode,0xc8指向'Ü' latin-1和Unicode,0xe9指向latin-1和Unicode的“é”。

在使用电子设备时,Unicode代码点需要一种有效的电子表示方式。 这就是编码scheme。 存在各种Unicode编码scheme(utf7,UTF-8,UTF-16,UTF-32)。 最直观,最直接的编码方法是简单地使用Unicode映射中的代码点值作为其电子表单的值,但Unicode目前有超过一百万个代码点,这意味着其中一些代码点需要3个字节expression。 为了高效地处理文本,1对1的映射将是不切实际的,因为它要求所有的代码点被存储在完全相同的空间中,每个字符至less有3个字节,而不pipe它们的实际需要如何。

大多数编码scheme在空间要求上都有缺点,最经济的编码方式并不包括所有的Unicode编码点,例如ascii只涵盖前128个,latin-1涵盖前256个。浪费,因为他们需要更多的字节超过必要的,即使是普通的“便宜”字符。 例如UTF-16,每个字符至less使用2个字节,包括那些在ASCII范围内('B'是65,仍然需要2个字节的UTF-16存储空间)的字符。 由于UTF-32以4字节存储所有字符,因此更加浪费。

UTF-8碰巧巧妙地解决了这个难题,一个能够存储具有可变数量的字节空间的代码点的scheme。 作为其编码策略的一部分,UTF-8将带有标志位的代码点(可能是为了解码器)标明它们的空间需求和边界。

unicode代码点在ASCII范围(0-127)中的UTF-8编码:

 0xxx xxxx (in binary) 
  • x表示在编码期间保留用于“存储”代码点的实际空间
  • 前面的0是一个标志,向UTF-8解码器表明这个编码点只需要1个字节。
  • 在编码时,UTF-8不会改变该特定范围内的代码点的值(即以UTF-8编码的65也是65)。 考虑到Unicode和ASCII在相同的范围内也是兼容的,偶然地使UTF-8和ASCII在该范围内也是兼容的。

例如,'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) 
  • 前导比特'110'向UTF-8解码器指示以2字节编码的码点的开始,而'1110'指示3个字节,11110将指示4个字节等等。
  • 内部“10”标志位用来表示内部字节的开始。
  • 再次,x标记编码后存储Unicode代码点值的空间。

例如'é',Unicode代码点是0xe9(233)。

 1110 1001 <-- 0xe9 

当UTF-8编码该值时,它确定该值大于127且小于2048,因此应该以2个字节编码:

 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 

UTF-8编码之后的0xe9 Unicode代码点变为0xc3a9。 terminal如何接收 如果您的terminal设置为使用latin-1(其中一个非unicode遗留编码)对string进行解码,您将看到é,因为它恰好在latin-1中的0xc3指向Ã和0xa9到©。

当Unicode字符被打印到标准输出时,使用sys.stdout.encoding 。 假设一个非Unicode字符在sys.stdout.encoding ,并且只发送给terminal。 在我的系统上:

 >>> import unicodedata as ud >>> import sys >>> sys.stdout.encoding 'cp437' >>> ud.name(u'\xe9') 'LATIN SMALL LETTER E WITH ACUTE' >>> ud.name('\xe9'.decode('cp437')) 'GREEK CAPITAL LETTER THETA' >>> import unicodedata as ud >>> ud.name(u'\xe9') 'LATIN SMALL LETTER E WITH ACUTE' >>> '\xe9'.decode('cp437') u'\u0398' >>> ud.name(u'\u0398') 'GREEK CAPITAL LETTER THETA' >>> print u'\xe9' é >>> print '\xe9' Θ 

sys.getdefaultencoding()仅在Python没有其他选项时使用。

Python REPL会尝试从您的环境中select要使用的编码。 如果它发现一些理智,那么这一切都只是工作。 这是什么时候它无法弄清楚它发生了什么事情。

 >>> print sys.stdout.encoding UTF-8 

通过input明确的Unicodestring来指定编码。 比较不使用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')